The maximum resolution for sam9x7 is 2560x1920. And its is 3264x2464 for sama7g5. Signed-off-by: Balamanikandan Gunasundar --- .../media/platform/microchip/microchip-sama7g5-isc.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index b0302dfc3278..36c3f4ba1962 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -55,6 +55,9 @@ #define ISC_SAMA7G5_MAX_SUPPORT_WIDTH 3264 #define ISC_SAMA7G5_MAX_SUPPORT_HEIGHT 2464 +#define ISC_SAM9X7_MAX_SUPPORT_WIDTH 2560 +#define ISC_SAM9X7_MAX_SUPPORT_HEIGHT 1920 + #define ISC_SAMA7G5_PIPELINE \ (WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \ CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE) @@ -432,8 +435,13 @@ static int microchip_xisc_probe(struct platform_device *pdev) isc->gamma_table = isc_sama7g5_gamma_table; isc->gamma_max = 0; - isc->max_width = ISC_SAMA7G5_MAX_SUPPORT_WIDTH; - isc->max_height = ISC_SAMA7G5_MAX_SUPPORT_HEIGHT; + if ((of_machine_is_compatible("microchip,sam9x7"))) { + isc->max_width = ISC_SAM9X7_MAX_SUPPORT_WIDTH; + isc->max_height = ISC_SAM9X7_MAX_SUPPORT_HEIGHT; + } else { + isc->max_width = ISC_SAMA7G5_MAX_SUPPORT_WIDTH; + isc->max_height = ISC_SAMA7G5_MAX_SUPPORT_HEIGHT; + } isc->config_dpc = isc_sama7g5_config_dpc; isc->config_csc = isc_sama7g5_config_csc; -- 2.34.1 Add DPC_DPCENABLE, DPC_GDCENABLE, and DPC_BLCENABLE enable bits to ISC_SAMA7G5_PIPELINE macro to prevent isc_sama7g5_adapt_pipeline() from masking out DPC modules during pipeline configuration Signed-off-by: Balamanikandan Gunasundar --- drivers/media/platform/microchip/microchip-sama7g5-isc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index 36c3f4ba1962..03f7a46acd47 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -59,7 +59,8 @@ #define ISC_SAM9X7_MAX_SUPPORT_HEIGHT 1920 #define ISC_SAMA7G5_PIPELINE \ - (WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \ + (DPC_DPCENABLE | DPC_GDCENABLE | DPC_BLCENABLE | \ + WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \ CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE) /* This is a list of the formats that the ISC can *output* */ -- 2.34.1 From: Balakrishnan Sambath Include DPC_GDCENABLE and CBC_ENABLE flags in pipeline configuration for RGB raw formats to enable green disparity correction and contrast/brightness control. Signed-off-by: Balakrishnan Sambath --- drivers/media/platform/microchip/microchip-isc-base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index a7cdc743fda7..c138e92a1aca 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -787,7 +787,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc) if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { isc->try_config.bits_pipeline = CFA_ENABLE | WB_ENABLE | GAM_ENABLES | DPC_BLCENABLE | - CC_ENABLE; + DPC_GDCENABLE | CBC_ENABLE | CC_ENABLE; } else { isc->try_config.bits_pipeline = 0x0; } -- 2.34.1 From: Balakrishnan Sambath Replace simple min/max detection with smart outlier rejection that skips bottom/top 2% of histogram to avoid noise and saturation. Add channel average calculation using weighted pixel intensity instead of simple pixel counting for more accurate color analysis. Signed-off-by: Balakrishnan Sambath --- .../platform/microchip/microchip-isc-base.c | 83 ++++++++++++++++--- .../media/platform/microchip/microchip-isc.h | 2 + 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index c138e92a1aca..956bdea830e3 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -262,6 +262,10 @@ static void isc_set_histogram(struct isc_device *isc, bool enable) struct isc_ctrls *ctrls = &isc->ctrls; if (enable) { + /* Initialize histogram data storage for clean start */ + memset(ctrls->total_pixels, 0, sizeof(ctrls->total_pixels)); + memset(ctrls->hist_minmax, 0, sizeof(ctrls->hist_minmax)); + regmap_write(regmap, ISC_HIS_CFG + isc->offsets.his, ISC_HIS_CFG_MODE_GR | (isc->config.sd_format->cfa_baycfg @@ -1231,24 +1235,83 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max) regmap_bulk_read(regmap, ISC_HIS_ENTRY + isc->offsets.his_entry, hist_entry, HIST_ENTRIES); - *hist_count = 0; - /* - * we deliberately ignore the end of the histogram, - * the most white pixels - */ + /* Calculate total pixels */ + u32 total_pixels = 0; + + for (i = 0; i < HIST_ENTRIES; i++) + total_pixels += hist_entry[i]; + + /* Handle empty histogram case */ + if (total_pixels == 0) { + *hist_count = 0; + ctrls->channel_avg[ctrls->hist_id] = 256; /* Default middle value */ + ctrls->total_pixels[ctrls->hist_id] = 0; + *min = 1; + *max = HIST_ENTRIES - 1; + dev_dbg(isc->dev, "isc wb: no pixels in histogram for channel %u", ctrls->hist_id); + return; + } + + /* Smart outlier rejection - skip bottom/top 2% */ + u32 dark_threshold = total_pixels / 50; /* Bottom 2% */ + u32 bright_threshold = total_pixels / 50; /* Top 2% */ + u32 cumulative = 0; + + /* Find effective minimum (skip dark noise) */ + *min = 1; for (i = 1; i < HIST_ENTRIES; i++) { - if (*hist_entry && !*min) + cumulative += hist_entry[i]; + if (cumulative > dark_threshold) { *min = i; - if (*hist_entry) + break; + } + } + + /* Find effective maximum (skip bright saturation) */ + cumulative = 0; + *max = HIST_ENTRIES - 1; + for (i = HIST_ENTRIES - 1; i > *min; i--) { + cumulative += hist_entry[i]; + if (cumulative > bright_threshold) { *max = i; - *hist_count += i * (*hist_entry++); + break; + } } + /* Ensure reasonable range */ + if (*max <= *min) { + *min = HIST_ENTRIES / 4; + *max = (HIST_ENTRIES * 3) / 4; + } + + /* Calculate both pixel count and weighted average for useful range */ + *hist_count = 0; + u64 weighted_sum = 0; + + for (i = *min; i <= *max; i++) { + u32 pixel_count = hist_entry[i]; + *hist_count += pixel_count; + weighted_sum += (u64)i * pixel_count; + } + + /* Store total useful pixels for this channel */ + ctrls->total_pixels[ctrls->hist_id] = *hist_count; + + /* Calculate channel average */ + if (*hist_count > 0) + ctrls->channel_avg[ctrls->hist_id] = + div64_u64(weighted_sum, *hist_count); + else + /* Default middle value */ + ctrls->channel_avg[ctrls->hist_id] = 256; + if (!*min) *min = 1; - dev_dbg(isc->dev, "isc wb: hist_id %u, hist_count %u", - ctrls->hist_id, *hist_count); + dev_dbg(isc->dev, + "isc wb: hist_id %u, avg %u, count %u, range [%u,%u], total %u", + ctrls->hist_id, ctrls->channel_avg[ctrls->hist_id], + *hist_count, *min, *max, total_pixels); } static void isc_wb_update(struct isc_ctrls *ctrls) diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h index ad4e98a1dd8f..bd75ff4f109b 100644 --- a/drivers/media/platform/microchip/microchip-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -156,6 +156,8 @@ struct isc_ctrls { #define HIST_MIN_INDEX 0 #define HIST_MAX_INDEX 1 u32 hist_minmax[HIST_BAYER][2]; + u32 channel_avg[HIST_BAYER]; /* Average pixel intensity per channel */ + u32 total_pixels[HIST_BAYER]; /* Total pixels per channel */ }; #define ISC_PIPE_LINE_NODE_NUM 15 -- 2.34.1 From: Balakrishnan Sambath Replace pixel counting with actual pixel intensity averages in Grey World algorithm for more accurate white balance calculation.This provides better color correction especially in mixed lighting conditions. Signed-off-by: Balakrishnan Sambath --- .../platform/microchip/microchip-isc-base.c | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index 956bdea830e3..bb2dd69a83f0 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1317,7 +1317,6 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max) static void isc_wb_update(struct isc_ctrls *ctrls) { struct isc_device *isc = container_of(ctrls, struct isc_device, ctrls); - u32 *hist_count = &ctrls->hist_count[0]; u32 c, offset[4]; u64 avg = 0; /* We compute two gains, stretch gain and grey world gain */ @@ -1328,10 +1327,10 @@ static void isc_wb_update(struct isc_ctrls *ctrls) * them towards the green channel. * Thus we want to keep Green as fixed and adjust only Red/Blue * Compute the average of the both green channels first + * Use channel averages for Grey World algorithm */ - avg = (u64)hist_count[ISC_HIS_CFG_MODE_GR] + - (u64)hist_count[ISC_HIS_CFG_MODE_GB]; - avg >>= 1; + avg = (ctrls->channel_avg[ISC_HIS_CFG_MODE_GR] + + ctrls->channel_avg[ISC_HIS_CFG_MODE_GB]) >> 1; dev_dbg(isc->dev, "isc wb: green components average %llu\n", avg); @@ -1340,6 +1339,11 @@ static void isc_wb_update(struct isc_ctrls *ctrls) return; for (c = ISC_HIS_CFG_MODE_GR; c <= ISC_HIS_CFG_MODE_B; c++) { + u32 hist_min = ctrls->hist_minmax[c][HIST_MIN_INDEX]; + u32 hist_max = ctrls->hist_minmax[c][HIST_MAX_INDEX]; + u32 channel_avg = ctrls->channel_avg[c]; + u32 total_pixels = ctrls->total_pixels[c]; + /* * the color offset is the minimum value of the histogram. * we stretch this color to the full range by substracting @@ -1373,23 +1377,21 @@ static void isc_wb_update(struct isc_ctrls *ctrls) * decimals */ s_gain[c] = (HIST_ENTRIES << 9) / - (ctrls->hist_minmax[c][HIST_MAX_INDEX] - - ctrls->hist_minmax[c][HIST_MIN_INDEX] + 1); + (hist_max - hist_min + 1); /* - * Now we have to compute the gain w.r.t. the average. - * Add/lose gain to the component towards the average. - * If it happens that the component is zero, use the - * fixed point value : 1.0 gain. + * Grey World gain using channel averages + * This is much more accurate than using hist_count */ - if (hist_count[c]) - gw_gain[c] = div_u64(avg << 9, hist_count[c]); + if (channel_avg > 0 && total_pixels > 1000) + gw_gain[c] = div64_u64((avg << 9), channel_avg); else gw_gain[c] = 1 << 9; dev_dbg(isc->dev, - "isc wb: component %d, s_gain %u, gw_gain %u\n", - c, s_gain[c], gw_gain[c]); + "isc wb: component %d, black_level=%u, avg=%u, s_gain=%u, gw_gain=%u", + c, hist_min, channel_avg, s_gain[c], gw_gain[c]); + /* multiply both gains and adjust for decimals */ ctrls->gain[c] = s_gain[c] * gw_gain[c]; ctrls->gain[c] >>= 9; @@ -1397,8 +1399,9 @@ static void isc_wb_update(struct isc_ctrls *ctrls) /* make sure we are not out of range */ ctrls->gain[c] = clamp_val(ctrls->gain[c], 0, GENMASK(12, 0)); - dev_dbg(isc->dev, "isc wb: component %d, final gain %u\n", - c, ctrls->gain[c]); + dev_dbg(isc->dev, + "isc wb: component %d, final gain %u, offset %d\n", + c, ctrls->gain[c], ctrls->offset[c]); } } -- 2.34.1 From: Balakrishnan Sambath Add adaptive black level offset based on histogram minimum and pixel statistics. Apply conservative correction for high black levels and gentle correction for low levels to prevent overcorrection and improve dynamic range. Signed-off-by: Balakrishnan Sambath --- .../platform/microchip/microchip-isc-base.c | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index bb2dd69a83f0..2706a27a2506 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1349,7 +1349,27 @@ static void isc_wb_update(struct isc_ctrls *ctrls) * we stretch this color to the full range by substracting * this value from the color component. */ - offset[c] = ctrls->hist_minmax[c][HIST_MIN_INDEX]; + if (hist_min > 5 && hist_min < 60 && total_pixels > 1000) { + /* + * Basic adaptive black level offset correction + * (Simplified version for kernel fallback) + */ + if (hist_min > 20) + /* Conservative for high levels */ + offset[c] = hist_min - 4; + else if (hist_min > 10) + /* Moderate correction */ + offset[c] = hist_min - 2; + else + /* Gentle correction */ + offset[c] = hist_min - 1; + + offset[c] = max(1U, offset[c]); /* Ensure minimum of 1 */ + } else { + /* Use default behavior for edge cases */ + offset[c] = hist_min; + } + /* * The offset is always at least 1. If the offset is 1, we do * not need to adjust it, so our result must be zero. -- 2.34.1 From: Balakrishnan Sambath Add support for multiple levels of gamma correction options (1/2.4, 1/2.2, 1/1.8) which can be modified through V4L2 controls(with range 0-2), with a default of 1/2.2(index 1). Signed-off-by: Balakrishnan Sambath Signed-off-by: Balamanikandan Gunasundar --- .../platform/microchip/microchip-isc-base.c | 3 +- .../microchip/microchip-sama7g5-isc.c | 54 ++++++++++++++----- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index 2706a27a2506..cd379f95fc1c 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1715,8 +1715,7 @@ static int isc_ctrl_init(struct isc_device *isc) ctrls->brightness = 0; v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -1024, 1023, 1, 0); - v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, isc->gamma_max, 1, - isc->gamma_max); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, isc->gamma_max, 1, 1); isc->awb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops, V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index 03f7a46acd47..d3ed9cfe6686 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -320,21 +320,47 @@ static void isc_sama7g5_adapt_pipeline(struct isc_device *isc) isc->try_config.bits_pipeline &= ISC_SAMA7G5_PIPELINE; } -/* Gamma table with gamma 1/2.2 */ +/* Gamma tables with gamma values 0.42, 0.45(Default), 0.56 */ static const u32 isc_sama7g5_gamma_table[][GAMMA_ENTRIES] = { - /* index 0 --> gamma bipartite */ + /* index 0 --> gamma bipartite 1/2.4(=0.42) */ { - 0x980, 0x4c0320, 0x650260, 0x7801e0, 0x8701a0, 0x940180, - 0xa00160, 0xab0120, 0xb40120, 0xbd0120, 0xc60100, 0xce0100, - 0xd600e0, 0xdd00e0, 0xe400e0, 0xeb00c0, 0xf100c0, 0xf700c0, - 0xfd00c0, 0x10300a0, 0x10800c0, 0x10e00a0, 0x11300a0, 0x11800a0, - 0x11d00a0, 0x12200a0, 0x12700a0, 0x12c0080, 0x13000a0, 0x1350080, - 0x13900a0, 0x13e0080, 0x1420076, 0x17d0062, 0x1ae0054, 0x1d8004a, - 0x1fd0044, 0x21f003e, 0x23e003a, 0x25b0036, 0x2760032, 0x28f0030, - 0x2a7002e, 0x2be002c, 0x2d4002c, 0x2ea0028, 0x2fe0028, 0x3120026, - 0x3250024, 0x3370024, 0x3490022, 0x35a0022, 0x36b0020, 0x37b0020, - 0x38b0020, 0x39b001e, 0x3aa001e, 0x3b9001c, 0x3c7001c, 0x3d5001c, - 0x3e3001c, 0x3f1001c, 0x3ff001a, 0x40c001a }, + 0x940, 0x4b0310, 0x630250, 0x7601d0, 0x840190, 0x910170, + 0x9d0150, 0xa80110, 0xb10110, 0xba0110, 0xc300f0, 0xcb00f0, + 0xd300e0, 0xda00e0, 0xe100c0, 0xe800c0, 0xee00c0, 0xf400c0, + 0xfa00a0, 0x10000a0, 0x10500a0, 0x10b00a0, 0x11000a0, 0x11500a0, + 0x11a0080, 0x11f0080, 0x1240080, 0x1290080, 0x12e0080, 0x1330070, + 0x1380070, 0x13c0070, 0x1410070, 0x17a0060, 0x1aa0052, 0x1d40046, + 0x1f90042, 0x21b003c, 0x23a0038, 0x2570034, 0x2720030, 0x28b002e, + 0x2a3002c, 0x2ba002a, 0x2d0002a, 0x2e60028, 0x2fa0026, 0x30e0026, + 0x3210024, 0x3330022, 0x3450022, 0x3560020, 0x3670020, 0x3770020, + 0x387001e, 0x396001e, 0x3a5001c, 0x3b3001c, 0x3c1001c, 0x3cf001a, + 0x3dd001a, 0x3eb0018, 0x3f90018 }, + /* index 1 --> gamma bipartite 1/2.2(=0.45) */ + { + 0x980, 0x4c0320, 0x650260, 0x7801e0, 0x8701a0, 0x940180, + 0xa00160, 0xab0120, 0xb40120, 0xbd0120, 0xc60100, 0xce0100, + 0xd600e0, 0xdd00e0, 0xe400e0, 0xeb00c0, 0xf100c0, 0xf700c0, + 0xfd00c0, 0x10300a0, 0x10800c0, 0x10e00a0, 0x11300a0, 0x11800a0, + 0x11d00a0, 0x12200a0, 0x12700a0, 0x12c0080, 0x13000a0, 0x1350080, + 0x13900a0, 0x13e0080, 0x1420076, 0x17d0062, 0x1ae0054, 0x1d8004a, + 0x1fd0044, 0x21f003e, 0x23e003a, 0x25b0036, 0x2760032, 0x28f0030, + 0x2a7002e, 0x2be002c, 0x2d4002c, 0x2ea0028, 0x2fe0028, 0x3120026, + 0x3250024, 0x3370024, 0x3490022, 0x35a0022, 0x36b0020, 0x37b0020, + 0x38b0020, 0x39b001e, 0x3aa001e, 0x3b9001c, 0x3c7001c, 0x3d5001c, + 0x3e3001c, 0x3f1001c, 0x3ff001a, 0x40c001a }, + /* index 2 --> gamma bipartite 1/1.8(=0.56) */ + { + 0xa62, 0x4f0350, 0x680280, 0x7e0200, 0x8d01c0, 0x9a01a0, + 0xa50180, 0xb00140, 0xb90140, 0xc20120, 0xcb0120, 0xd30100, + 0xdb0100, 0xe300e0, 0xea00e0, 0xf100e0, 0xf700c0, 0xfd00c0, + 0x10300c0, 0x10900a0, 0x10e00a0, 0x11400a0, 0x11900a0, 0x11e00a0, + 0x12300a0, 0x12800a0, 0x12d0080, 0x1320080, 0x1370080, 0x13c0080, + 0x1410080, 0x1460080, 0x14a0070, 0x1830060, 0x1b40052, 0x1df0048, + 0x2040042, 0x2250040, 0x2440038, 0x2600036, 0x27b0032, 0x2940030, + 0x2ac002e, 0x2c4002c, 0x2da002a, 0x2f0002a, 0x3050028, 0x3190026, + 0x32c0026, 0x33e0024, 0x3500024, 0x3610022, 0x3720020, 0x3820020, + 0x3920020, 0x3a2001e, 0x3b1001e, 0x3c0001c, 0x3ce001c, 0x3dc001c, + 0x3ea001a, 0x3f8001a, 0x4060018, 0x4130018 }, }; static int xisc_parse_dt(struct device *dev, struct isc_device *isc) @@ -434,7 +460,7 @@ static int microchip_xisc_probe(struct platform_device *pdev) } isc->gamma_table = isc_sama7g5_gamma_table; - isc->gamma_max = 0; + isc->gamma_max = 2; if ((of_machine_is_compatible("microchip,sam9x7"))) { isc->max_width = ISC_SAM9X7_MAX_SUPPORT_WIDTH; -- 2.34.1 From: Balakrishnan Sambath Add new histogram submodule driver to export raw histogram statistics and data to userspace. Signed-off-by: Balakrishnan Sambath --- drivers/media/platform/microchip/Kconfig | 2 + drivers/media/platform/microchip/Makefile | 2 +- .../platform/microchip/microchip-isc-stats.c | 549 ++++++++++++++++++ .../media/platform/microchip/microchip-isc.h | 24 + 4 files changed, 576 insertions(+), 1 deletion(-) create mode 100644 drivers/media/platform/microchip/microchip-isc-stats.c diff --git a/drivers/media/platform/microchip/Kconfig b/drivers/media/platform/microchip/Kconfig index 4734ecced029..2864a57e2ff4 100644 --- a/drivers/media/platform/microchip/Kconfig +++ b/drivers/media/platform/microchip/Kconfig @@ -10,6 +10,7 @@ config VIDEO_MICROCHIP_ISC select MEDIA_CONTROLLER select VIDEO_V4L2_SUBDEV_API select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC select REGMAP_MMIO select V4L2_FWNODE select VIDEO_MICROCHIP_ISC_BASE @@ -26,6 +27,7 @@ config VIDEO_MICROCHIP_XISC depends on VIDEO_DEV && COMMON_CLK depends on ARCH_AT91 || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC select REGMAP_MMIO select V4L2_FWNODE select VIDEO_MICROCHIP_ISC_BASE diff --git a/drivers/media/platform/microchip/Makefile b/drivers/media/platform/microchip/Makefile index bd8d6e779c51..94c64d3d242c 100644 --- a/drivers/media/platform/microchip/Makefile +++ b/drivers/media/platform/microchip/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only microchip-isc-objs = microchip-sama5d2-isc.o microchip-xisc-objs = microchip-sama7g5-isc.o -microchip-isc-common-objs = microchip-isc-base.o microchip-isc-clk.o microchip-isc-scaler.o +microchip-isc-common-objs = microchip-isc-base.o microchip-isc-clk.o microchip-isc-scaler.o microchip-isc-stats.o obj-$(CONFIG_VIDEO_MICROCHIP_ISC_BASE) += microchip-isc-common.o obj-$(CONFIG_VIDEO_MICROCHIP_ISC) += microchip-isc.o diff --git a/drivers/media/platform/microchip/microchip-isc-stats.c b/drivers/media/platform/microchip/microchip-isc-stats.c new file mode 100644 index 000000000000..d7813c9d95ac --- /dev/null +++ b/drivers/media/platform/microchip/microchip-isc-stats.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Microchip ISC Driver - Statistics Subdevice + * Raw Histogram Export for Userspace Applications + * + * Copyright (C) 2025 Microchip Technology Inc. + * + * Author: Balakrishnan Sambath + */ + +#include +#include +#include +#include +#include +#include +#include "microchip-isc-regs.h" +#include "microchip-isc.h" + +#define ISC_STATS_DEV_NAME "microchip-isc_stats" +#define ISC_STATS_MIN_BUFS 2 +#define ISC_STATS_MAX_BUFS 8 + +/** + * struct isc_stat_buffer - Raw histogram statistics buffer structure + * @frame_number: Sequential frame number from capture + * @timestamp: Frame capture timestamp in nanoseconds + * @meas_type: Bitmask of measurement types available (ISC_CIF_ISP_STAT_*) + * @hist: Array of histogram data for each Bayer channel + * @hist.hist_bins: Raw 512-bin histogram data from hardware + * @hist.hist_min: Minimum pixel value observed in channel + * @hist.hist_max: Maximum pixel value observed in channel + * @hist.total_pixels: Total number of pixels processed in channel + * @valid_channels: Bitmask indicating which Bayer channels contain valid data + * @bayer_pattern: Current Bayer pattern configuration (CFA_BAYCFG_*) + * @reserved: Padding for future expansion and alignment + * + * This structure contains raw, unprocessed histogram data from the ISC + * hardware for all four Bayer channels (GR, R, GB, B). No algorithmic + * processing is performed - data is exported directly from hardware + * registers for userspace processing applications. + */ +struct isc_stat_buffer { + u32 frame_number; + u64 timestamp; + u32 meas_type; + + struct { + u32 hist_bins[HIST_ENTRIES]; + u32 hist_min; + u32 hist_max; + u32 total_pixels; + } hist[HIST_BAYER]; + + u8 valid_channels; + u8 bayer_pattern; + u16 reserved[2]; +} __packed; + +/* Statistics measurement type flags */ +#define ISC_CIF_ISP_STAT_HIST BIT(0) + +static bool isc_stats_in_use(struct isc_stats *stats) +{ + struct video_device *vdev; + + if (!stats || !stats->isc) + return false; + + vdev = &stats->vnode.vdev; + return vdev && video_is_registered(vdev) && !list_empty(&vdev->fh_list); +} + +static bool isc_stats_has_bufs(struct isc_stats *stats) +{ + bool has_buffers; + + if (!stats) + return false; + + spin_lock(&stats->lock); + has_buffers = !list_empty(&stats->stat); + spin_unlock(&stats->lock); + + return has_buffers; +} + +/* + * V4L2 device operations + */ + +static int isc_stats_enum_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct video_device *video = video_devdata(file); + struct isc_stats *stats = video_get_drvdata(video); + + if (f->index > 0 || f->type != video->queue->type) + return -EINVAL; + + f->pixelformat = stats->vdev_fmt.fmt.meta.dataformat; + return 0; +} + +static int isc_stats_g_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct video_device *video = video_devdata(file); + struct isc_stats *stats = video_get_drvdata(video); + struct v4l2_meta_format *meta = &f->fmt.meta; + + if (f->type != video->queue->type) + return -EINVAL; + + memset(meta, 0, sizeof(*meta)); + meta->dataformat = stats->vdev_fmt.fmt.meta.dataformat; + meta->buffersize = stats->vdev_fmt.fmt.meta.buffersize; + + return 0; +} + +static int isc_stats_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct video_device *vdev = video_devdata(file); + + strscpy(cap->driver, "microchip-isc", sizeof(cap->driver)); + strscpy(cap->card, vdev->name, sizeof(cap->card)); + strscpy(cap->bus_info, "platform:microchip-isc", sizeof(cap->bus_info)); + + return 0; +} + +static int isc_stats_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct isc_stats *stats = video_get_drvdata(vdev); + + dev_dbg(stats->isc->dev, "Stats device opened by %s (pid %d)\n", + current->comm, current->pid); + + return v4l2_fh_open(file); +} + +static int isc_stats_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct isc_stats *stats = video_get_drvdata(vdev); + + dev_dbg(stats->isc->dev, "Stats device closed by %s (pid %d)\n", + current->comm, current->pid); + + return _vb2_fop_release(file, NULL); +} + +static const struct v4l2_ioctl_ops isc_stats_ioctl_ops = { + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_enum_fmt_meta_cap = isc_stats_enum_fmt_meta_cap, + .vidioc_g_fmt_meta_cap = isc_stats_g_fmt_meta_cap, + .vidioc_s_fmt_meta_cap = isc_stats_g_fmt_meta_cap, + .vidioc_try_fmt_meta_cap = isc_stats_g_fmt_meta_cap, + .vidioc_querycap = isc_stats_querycap, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations isc_stats_fops = { + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, + .poll = vb2_fop_poll, + .open = isc_stats_open, + .release = isc_stats_release +}; + +/* + * VB2 queue operations + */ + +static int isc_stats_vb2_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct isc_stats *stats = vq->drv_priv; + + *num_planes = 1; + *num_buffers = clamp_t(u32, *num_buffers, ISC_STATS_MIN_BUFS, + ISC_STATS_MAX_BUFS); + sizes[0] = sizeof(struct isc_stat_buffer); + + dev_dbg(stats->isc->dev, "Stats queue: %u buffers, %u bytes each\n", + *num_buffers, sizes[0]); + + return 0; +} + +static void isc_stats_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct isc_buffer *stats_buf = container_of(vbuf, struct isc_buffer, vb); + struct vb2_queue *vq = vb->vb2_queue; + struct isc_stats *stats_dev = vq->drv_priv; + + spin_lock_irq(&stats_dev->lock); + list_add_tail(&stats_buf->list, &stats_dev->stat); + spin_unlock_irq(&stats_dev->lock); + + dev_dbg(stats_dev->isc->dev, "Stats buffer %d queued\n", vb->index); +} + +static int isc_stats_vb2_buf_prepare(struct vb2_buffer *vb) +{ + if (vb2_plane_size(vb, 0) < sizeof(struct isc_stat_buffer)) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, sizeof(struct isc_stat_buffer)); + return 0; +} + +static int isc_stats_vb2_start_streaming(struct vb2_queue *vq, + unsigned int count) +{ + struct isc_stats *stats = vq->drv_priv; + + dev_dbg(stats->isc->dev, "Stats streaming started\n"); + return 0; +} + +static void isc_stats_vb2_stop_streaming(struct vb2_queue *vq) +{ + struct isc_stats *stats = vq->drv_priv; + struct isc_buffer *buf; + unsigned int i; + + dev_dbg(stats->isc->dev, "Stats streaming stopped\n"); + + spin_lock_irq(&stats->lock); + for (i = 0; i < ISC_STATS_MAX_BUFS; i++) { + if (list_empty(&stats->stat)) + break; + buf = list_first_entry(&stats->stat, struct isc_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irq(&stats->lock); +} + +static const struct vb2_ops isc_stats_vb2_ops = { + .queue_setup = isc_stats_vb2_queue_setup, + .buf_queue = isc_stats_vb2_buf_queue, + .buf_prepare = isc_stats_vb2_buf_prepare, + .start_streaming = isc_stats_vb2_start_streaming, + .stop_streaming = isc_stats_vb2_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int isc_stats_init_vb2_queue(struct vb2_queue *q, + struct isc_stats *stats) +{ + struct isc_vdev_node *node; + + node = container_of(q, struct isc_vdev_node, buf_queue); + + q->type = V4L2_BUF_TYPE_META_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->drv_priv = stats; + q->ops = &isc_stats_vb2_ops; + q->mem_ops = &vb2_vmalloc_memops; + q->buf_struct_size = sizeof(struct isc_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &node->vlock; + + return vb2_queue_init(q); +} + +/* + * Histogram data processing + */ + +static void isc_stats_fill_data(struct isc_stats *stats, + struct isc_stat_buffer *pbuf) +{ + struct isc_device *isc = stats->isc; + struct isc_ctrls *ctrls = &isc->ctrls; + int c; + + pbuf->meas_type |= ISC_CIF_ISP_STAT_HIST; + + /* Copy existing histogram data from AWB work function */ + for (c = 0; c < HIST_BAYER; c++) { + memcpy(pbuf->hist[c].hist_bins, isc->full_hist_data[c], + sizeof(pbuf->hist[c].hist_bins)); + + pbuf->hist[c].hist_min = ctrls->hist_minmax[c][HIST_MIN_INDEX]; + pbuf->hist[c].hist_max = ctrls->hist_minmax[c][HIST_MAX_INDEX]; + pbuf->hist[c].total_pixels = ctrls->total_pixels[c]; + } + + /* Set valid channels - all 4 Bayer channels */ + pbuf->valid_channels = 0x0F; + + /* Set Bayer pattern */ + if (isc->config.sd_format) + pbuf->bayer_pattern = isc->config.sd_format->cfa_baycfg; + else + pbuf->bayer_pattern = 0; + + dev_dbg(isc->dev, + "Stats data ready: pixels=[%u,%u,%u,%u], valid_channels=0x%x\n", + pbuf->hist[0].total_pixels, pbuf->hist[1].total_pixels, + pbuf->hist[2].total_pixels, pbuf->hist[3].total_pixels, + pbuf->valid_channels); +} + +static void isc_stats_send_buf(struct isc_stats *stats) +{ + struct isc_stat_buffer *cur_stat_buf; + struct isc_buffer *cur_buf = NULL; + struct isc_device *isc = stats->isc; + unsigned int frame_sequence = isc->sequence; + u64 timestamp = ktime_get_ns(); + + /* Get one empty buffer from userspace */ + spin_lock(&stats->lock); + if (!list_empty(&stats->stat)) { + cur_buf = list_first_entry(&stats->stat, + struct isc_buffer, list); + list_del(&cur_buf->list); + } + spin_unlock(&stats->lock); + + if (!cur_buf) { + dev_dbg(isc->dev, "No stats buffer available\n"); + return; + } + + cur_stat_buf = vb2_plane_vaddr(&cur_buf->vb.vb2_buf, 0); + if (!cur_stat_buf) { + dev_err(isc->dev, "Failed to get stats buffer vaddr\n"); + goto error_return_buffer; + } + + /* Clear buffer and fill metadata */ + memset(cur_stat_buf, 0, sizeof(*cur_stat_buf)); + cur_stat_buf->frame_number = frame_sequence; + cur_stat_buf->timestamp = timestamp; + + /* Fill raw histogram data */ + isc_stats_fill_data(stats, cur_stat_buf); + + /* Send buffer to userspace */ + vb2_set_plane_payload(&cur_buf->vb.vb2_buf, 0, + sizeof(struct isc_stat_buffer)); + cur_buf->vb.sequence = frame_sequence; + cur_buf->vb.vb2_buf.timestamp = timestamp; + vb2_buffer_done(&cur_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + + dev_dbg(isc->dev, + "Stats sent: frame=%u, channels=[%u,%u,%u,%u] pixels\n", + frame_sequence, + cur_stat_buf->hist[0].total_pixels, + cur_stat_buf->hist[1].total_pixels, + cur_stat_buf->hist[2].total_pixels, + cur_stat_buf->hist[3].total_pixels); + return; + +error_return_buffer: + /* Return buffer to queue on error */ + spin_lock(&stats->lock); + list_add(&cur_buf->list, &stats->stat); + spin_unlock(&stats->lock); +} + +/* + * Public API functions + */ + +/** + * isc_stats_isr() - Process statistics in interrupt context + * @stats: ISC histogram statistics device + * + * Called from the ISC interrupt handler when histogram data is ready. + * Exports raw histogram data to userspace applications that have + * buffers queued on the statistics device. + */ +void isc_stats_isr(struct isc_stats *stats) +{ + if (!stats) { + pr_err("ISC stats: stats is NULL\n"); + return; + } + + if (!stats->isc) { + pr_err("ISC stats: stats->isc is NULL\n"); + return; + } + + /* Only send data if userspace is using the device */ + if (!isc_stats_in_use(stats)) { + dev_dbg(stats->isc->dev, "Stats device not in use\n"); + return; + } + + /* Only send data if userspace has queued buffers */ + if (!isc_stats_has_bufs(stats)) { + dev_dbg(stats->isc->dev, "No queued buffers\n"); + return; + } + + /* Send histogram data to userspace */ + isc_stats_send_buf(stats); +} +EXPORT_SYMBOL_GPL(isc_stats_isr); + +/** + * isc_stats_active() - Check if userspace is actively using stats + * @stats: ISC histogram statistics device + * + * Determines if any userspace application has the statistics device open + * and has queued buffers waiting for histogram data. + * + * Return: true if userspace is ready to receive data, false otherwise + */ +bool isc_stats_active(struct isc_stats *stats) +{ + return isc_stats_in_use(stats) && isc_stats_has_bufs(stats); +} +EXPORT_SYMBOL_GPL(isc_stats_active); + +static void isc_stats_init(struct isc_stats *stats) +{ + stats->vdev_fmt.fmt.meta.dataformat = V4L2_META_FMT_ISC_STAT_3A; + stats->vdev_fmt.fmt.meta.buffersize = sizeof(struct isc_stat_buffer); +} + +/** + * isc_stats_register() - Register statistics device + * @isc: ISC device + * + * Creates and registers a V4L2 video device for exporting raw histogram + * statistics to userspace. + * + */ +int isc_stats_register(struct isc_device *isc) +{ + struct isc_stats *stats = &isc->stats; + struct isc_vdev_node *node = &stats->vnode; + struct video_device *vdev = &node->vdev; + int ret; + + /* Initialize stats structure */ + stats->isc = isc; + mutex_init(&node->vlock); + INIT_LIST_HEAD(&stats->stat); + spin_lock_init(&stats->lock); + + /* Configure video device */ + strscpy(vdev->name, ISC_STATS_DEV_NAME, sizeof(vdev->name)); + vdev->ioctl_ops = &isc_stats_ioctl_ops; + vdev->fops = &isc_stats_fops; + vdev->release = video_device_release_empty; + vdev->lock = &node->vlock; + vdev->v4l2_dev = &isc->v4l2_dev; + vdev->queue = &node->buf_queue; + vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING; + vdev->vfl_dir = VFL_DIR_RX; + + /* Initialize VB2 queue */ + ret = isc_stats_init_vb2_queue(vdev->queue, stats); + if (ret) { + dev_err(isc->dev, "Failed to init stats VB2 queue: %d\n", ret); + goto error_cleanup; + } + + /* Initialize stats format */ + isc_stats_init(stats); + + video_set_drvdata(vdev, stats); + + node->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, &node->pad); + if (ret) { + dev_err(isc->dev, "Failed to init stats media entity: %d\n", ret); + goto error_cleanup; + } + + /* Register video device */ + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(isc->dev, "Failed to register stats device: %d\n", ret); + goto error_media_cleanup; + } + + dev_info(isc->dev, "Stats device registered as %s\n", + video_device_node_name(vdev)); + + return 0; + +error_media_cleanup: + media_entity_cleanup(&vdev->entity); +error_cleanup: + mutex_destroy(&node->vlock); + stats->isc = NULL; + return ret; +} +EXPORT_SYMBOL_GPL(isc_stats_register); + +/** + * isc_stats_unregister() - Unregister statistics device + * @isc: ISC device + * + * Unregisters and cleans up the statistics video device. + */ +void isc_stats_unregister(struct isc_device *isc) +{ + struct isc_stats *stats = &isc->stats; + struct isc_vdev_node *node = &stats->vnode; + struct video_device *vdev = &node->vdev; + + if (!stats->isc) + return; + + dev_dbg(isc->dev, "Unregistering stats device\n"); + + /* Unregister video device */ + vb2_video_unregister_device(vdev); + + media_entity_cleanup(&vdev->entity); + + /* Destroy synchronization primitives */ + mutex_destroy(&node->vlock); + + stats->isc = NULL; +} +EXPORT_SYMBOL_GPL(isc_stats_unregister); + +MODULE_AUTHOR("Balakrishnan Sambath "); +MODULE_DESCRIPTION("Microchip ISC Statistics Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h index bd75ff4f109b..5245e2790268 100644 --- a/drivers/media/platform/microchip/microchip-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -197,6 +197,23 @@ enum isc_scaler_pads { ISC_SCALER_PADS_NUM = 2, }; +/* Video device node structure */ +struct isc_vdev_node { + struct video_device vdev; + struct vb2_queue buf_queue; + struct mutex vlock; /* lock for video node */ + struct media_pad pad; +}; + +/* Statistics device structure */ +struct isc_stats { + struct isc_device *isc; + struct isc_vdev_node vnode; + struct list_head stat; + spinlock_t lock; /* lock for buffers */ + struct v4l2_format vdev_fmt; +}; + /* * struct isc_device - ISC device driver data/config struct * @regmap: Register map @@ -340,6 +357,9 @@ struct isc_device { struct v4l2_ctrl *gb_off_ctrl; }; + /* Statistics device */ + struct isc_stats stats; + #define GAMMA_ENTRIES 64 /* pointer to the defined gamma table */ const u32 (*gamma_table)[GAMMA_ENTRIES]; @@ -396,6 +416,10 @@ int isc_scaler_link(struct isc_device *isc); int isc_scaler_init(struct isc_device *isc); int isc_mc_init(struct isc_device *isc, u32 ver); void isc_mc_cleanup(struct isc_device *isc); +int isc_stats_register(struct isc_device *isc); +void isc_stats_unregister(struct isc_device *isc); +void isc_stats_isr(struct isc_stats *stats); +bool isc_stats_active(struct isc_stats *stats); struct isc_format *isc_find_format_by_code(struct isc_device *isc, unsigned int code, int *index); -- 2.34.1 From: Balakrishnan Sambath Register the statistics sub-device during async complete and ensure proper cleanup for the same in isc_mc_cleanup(). Signed-off-by: Balakrishnan Sambath Signed-off-by: Balamanikandan Gunasundar --- drivers/media/platform/microchip/microchip-isc-base.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index cd379f95fc1c..5b49b6ff5ae9 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1912,6 +1912,13 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) goto isc_async_complete_err; } + /* Register statistics device */ + ret = isc_stats_register(isc); + if (ret) { + dev_err(isc->dev, "Failed to register stats device: %d\n", ret); + goto isc_async_complete_unregister_device; + } + ret = isc_scaler_link(isc); if (ret < 0) goto isc_async_complete_unregister_device; @@ -2036,6 +2043,7 @@ void isc_mc_cleanup(struct isc_device *isc) { media_entity_cleanup(&isc->video_dev.entity); media_device_cleanup(&isc->mdev); + isc_stats_unregister(isc); } EXPORT_SYMBOL_GPL(isc_mc_cleanup); -- 2.34.1 From: Balakrishnan Sambath Remove AWB dependency to allow histogram collection even when AWB is handled in userspace. Signed-off-by: Balakrishnan Sambath --- drivers/media/platform/microchip/microchip-isc-base.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index 5b49b6ff5ae9..e6d7f59893ac 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -318,8 +318,7 @@ static int isc_configure(struct isc_device *isc) * The current implemented histogram is available for RAW R, B, GB, GR * channels. We need to check if sensor is outputting RAW BAYER */ - if (isc->ctrls.awb && - ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) + if (ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) isc_set_histogram(isc, true); else isc_set_histogram(isc, false); -- 2.34.1 Expose hue and saturation as adjustable controls allowing users to modify it. Write the user specified values to the hardware registers. Additionally write the brightness and contrast values to the registers that were missing. Signed-off-by: Balamanikandan Gunasundar --- .../platform/microchip/microchip-isc-base.c | 17 +++++++++++++++++ .../platform/microchip/microchip-isc-regs.h | 3 +++ .../media/platform/microchip/microchip-isc.h | 2 ++ 3 files changed, 22 insertions(+) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index e6d7f59893ac..ce22b4789ebd 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1520,6 +1520,7 @@ static int isc_s_ctrl(struct v4l2_ctrl *ctrl) struct isc_device *isc = container_of(ctrl->handler, struct isc_device, ctrls.handler); struct isc_ctrls *ctrls = &isc->ctrls; + struct regmap *regmap = isc->regmap; if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) return 0; @@ -1527,9 +1528,19 @@ static int isc_s_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: ctrls->brightness = ctrl->val & ISC_CBC_BRIGHT_MASK; + regmap_write(regmap, ISC_CBC_BRIGHT + isc->offsets.cbc, ctrls->brightness); break; case V4L2_CID_CONTRAST: ctrls->contrast = ctrl->val & ISC_CBC_CONTRAST_MASK; + regmap_write(regmap, ISC_CBC_CONTRAST + isc->offsets.cbc, ctrls->contrast); + break; + case V4L2_CID_HUE: + ctrls->hue = ctrl->val & ISC_CBCHS_HUE_MASK; + regmap_write(regmap, ISC_CBCHS_HUE, ctrls->hue); + break; + case V4L2_CID_SATURATION: + ctrls->saturation = ctrl->val & ISC_CBCHS_SAT_MASK; + regmap_write(regmap, ISC_CBCHS_SAT, ctrls->saturation); break; case V4L2_CID_GAMMA: ctrls->gamma_index = ctrl->val; @@ -1538,6 +1549,10 @@ static int isc_s_ctrl(struct v4l2_ctrl *ctrl) return -EINVAL; } + mutex_lock(&isc->awb_mutex); + isc_update_profile(isc); + mutex_unlock(&isc->awb_mutex); + return 0; } @@ -1714,6 +1729,8 @@ static int isc_ctrl_init(struct isc_device *isc) ctrls->brightness = 0; v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -1024, 1023, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE, -180, 180, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, 0, 100, 1, 16); v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, isc->gamma_max, 1, 1); isc->awb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops, V4L2_CID_AUTO_WHITE_BALANCE, diff --git a/drivers/media/platform/microchip/microchip-isc-regs.h b/drivers/media/platform/microchip/microchip-isc-regs.h index e77e1d9a1db8..2593bd533cac 100644 --- a/drivers/media/platform/microchip/microchip-isc-regs.h +++ b/drivers/media/platform/microchip/microchip-isc-regs.h @@ -270,8 +270,11 @@ /* Hue Register */ #define ISC_CBCHS_HUE 0x4e0 +#define ISC_CBCHS_HUE_MASK GENMASK(8, 0) + /* Saturation Register */ #define ISC_CBCHS_SAT 0x4e4 +#define ISC_CBCHS_SAT_MASK GENMASK(11, 0) /* Offset for SUB422 register specific to sama5d2 product */ #define ISC_SAMA5D2_SUB422_OFFSET 0 diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h index 5245e2790268..7afba3c04dfb 100644 --- a/drivers/media/platform/microchip/microchip-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -139,6 +139,8 @@ struct isc_ctrls { u32 brightness; u32 contrast; + u32 hue; + u32 saturation; u8 gamma_index; #define ISC_WB_NONE 0 #define ISC_WB_AUTO 1 -- 2.34.1 As per the datasheet the more relevant name for this enable bit is "Contrast, Brightness, Hue and Saturation Control Enable" Signed-off-by: Balamanikandan Gunasundar --- drivers/media/platform/microchip/microchip-isc-base.c | 10 +++++----- drivers/media/platform/microchip/microchip-isc.h | 2 +- .../media/platform/microchip/microchip-sama5d2-isc.c | 2 +- .../media/platform/microchip/microchip-sama7g5-isc.c | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index ce22b4789ebd..e9bd191f042b 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -790,7 +790,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc) if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { isc->try_config.bits_pipeline = CFA_ENABLE | WB_ENABLE | GAM_ENABLES | DPC_BLCENABLE | - DPC_GDCENABLE | CBC_ENABLE | CC_ENABLE; + DPC_GDCENABLE | CBHS_ENABLE | CC_ENABLE; } else { isc->try_config.bits_pipeline = 0x0; } @@ -800,7 +800,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc) if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { isc->try_config.bits_pipeline = CFA_ENABLE | CSC_ENABLE | GAM_ENABLES | WB_ENABLE | - SUB420_ENABLE | SUB422_ENABLE | CBC_ENABLE | + SUB420_ENABLE | SUB422_ENABLE | CBHS_ENABLE | DPC_BLCENABLE; } else { isc->try_config.bits_pipeline = 0x0; @@ -811,7 +811,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc) if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { isc->try_config.bits_pipeline = CFA_ENABLE | CSC_ENABLE | WB_ENABLE | GAM_ENABLES | - SUB422_ENABLE | CBC_ENABLE | DPC_BLCENABLE; + SUB422_ENABLE | CBHS_ENABLE | DPC_BLCENABLE; } else { isc->try_config.bits_pipeline = 0x0; } @@ -823,7 +823,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc) if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { isc->try_config.bits_pipeline = CFA_ENABLE | CSC_ENABLE | WB_ENABLE | GAM_ENABLES | - SUB422_ENABLE | CBC_ENABLE | DPC_BLCENABLE; + SUB422_ENABLE | CBHS_ENABLE | DPC_BLCENABLE; } else { isc->try_config.bits_pipeline = 0x0; } @@ -834,7 +834,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc) if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { isc->try_config.bits_pipeline = CFA_ENABLE | CSC_ENABLE | WB_ENABLE | GAM_ENABLES | - CBC_ENABLE | DPC_BLCENABLE; + CBHS_ENABLE | DPC_BLCENABLE; } else { isc->try_config.bits_pipeline = 0x0; } diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h index 7afba3c04dfb..35dfd2501ea2 100644 --- a/drivers/media/platform/microchip/microchip-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -88,7 +88,7 @@ struct isc_format { #define GAM_RENABLE BIT(9) #define VHXS_ENABLE BIT(10) #define CSC_ENABLE BIT(11) -#define CBC_ENABLE BIT(12) +#define CBHS_ENABLE BIT(12) #define SUB422_ENABLE BIT(13) #define SUB420_ENABLE BIT(14) diff --git a/drivers/media/platform/microchip/microchip-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c index 66d3d7891991..239aac170472 100644 --- a/drivers/media/platform/microchip/microchip-sama5d2-isc.c +++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c @@ -54,7 +54,7 @@ #define ISC_SAMA5D2_PIPELINE \ (WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \ - CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE) + CBHS_ENABLE | SUB422_ENABLE | SUB420_ENABLE) /* This is a list of the formats that the ISC can *output* */ static const struct isc_format sama5d2_controller_formats[] = { diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index d3ed9cfe6686..985281c4dc76 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -61,7 +61,7 @@ #define ISC_SAMA7G5_PIPELINE \ (DPC_DPCENABLE | DPC_GDCENABLE | DPC_BLCENABLE | \ WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \ - CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE) + CBHS_ENABLE | SUB422_ENABLE | SUB420_ENABLE) /* This is a list of the formats that the ISC can *output* */ static const struct isc_format sama7g5_controller_formats[] = { -- 2.34.1 Store the histogram data for all 4 channels(R, B, GR, GB). The data of each channel is available on one interrupt. We need 4 interrupt cycles to get all four channels. Store all the channels in a 2D array. This data is later passed to the application buffer when it requests for a dequeue. Signed-off-by: Balamanikandan Gunasundar --- drivers/media/platform/microchip/microchip-isc-base.c | 2 +- drivers/media/platform/microchip/microchip-isc-stats.c | 2 +- drivers/media/platform/microchip/microchip-isc.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index e9bd191f042b..6651be6fcb57 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1225,7 +1225,7 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max) struct regmap *regmap = isc->regmap; struct isc_ctrls *ctrls = &isc->ctrls; u32 *hist_count = &ctrls->hist_count[ctrls->hist_id]; - u32 *hist_entry = &ctrls->hist_entry[0]; + u32 *hist_entry = &ctrls->hist_entry[ctrls->hist_id][0]; u32 i; *min = 0; diff --git a/drivers/media/platform/microchip/microchip-isc-stats.c b/drivers/media/platform/microchip/microchip-isc-stats.c index d7813c9d95ac..1f3d1ea75005 100644 --- a/drivers/media/platform/microchip/microchip-isc-stats.c +++ b/drivers/media/platform/microchip/microchip-isc-stats.c @@ -298,7 +298,7 @@ static void isc_stats_fill_data(struct isc_stats *stats, /* Copy existing histogram data from AWB work function */ for (c = 0; c < HIST_BAYER; c++) { - memcpy(pbuf->hist[c].hist_bins, isc->full_hist_data[c], + memcpy(pbuf->hist[c].hist_bins, &isc->ctrls.hist_entry[0][c], sizeof(pbuf->hist[c].hist_bins)); pbuf->hist[c].hist_min = ctrls->hist_minmax[c][HIST_MIN_INDEX]; diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h index 35dfd2501ea2..fcb20669ef69 100644 --- a/drivers/media/platform/microchip/microchip-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -151,7 +151,7 @@ struct isc_ctrls { u32 gain[HIST_BAYER]; s32 offset[HIST_BAYER]; - u32 hist_entry[HIST_ENTRIES]; + u32 hist_entry[HIST_BAYER][HIST_ENTRIES]; u32 hist_count[HIST_BAYER]; u8 hist_id; u8 hist_stat; -- 2.34.1 From: Balakrishnan Sambath Move hist_stat assignment before profile update to ensure histogram state is properly set when update_profile triggers register writes. This prevents race conditions during histogram initialization. Signed-off-by: Balakrishnan Sambath --- drivers/media/platform/microchip/microchip-isc-base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index 6651be6fcb57..e9f14de7ae32 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -275,10 +275,10 @@ static void isc_set_histogram(struct isc_device *isc, bool enable) ISC_HIS_CTRL_EN); regmap_write(regmap, ISC_INTEN, ISC_INT_HISDONE); ctrls->hist_id = ISC_HIS_CFG_MODE_GR; + ctrls->hist_stat = HIST_ENABLED; isc_update_profile(isc); regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ); - ctrls->hist_stat = HIST_ENABLED; } else { regmap_write(regmap, ISC_INTDIS, ISC_INT_HISDONE); regmap_write(regmap, ISC_HIS_CTRL + isc->offsets.his, -- 2.34.1 From: Balakrishnan Sambath Fix histogram cycling stopping after first channel when AWB is disabled. Now the histogram cycling will continue even when AWB is disabled, enabling hardware histogram data export to userspace. Signed-off-by: Balakrishnan Sambath --- drivers/media/platform/microchip/microchip-isc-base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index e9f14de7ae32..cafd05244db2 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1509,7 +1509,7 @@ static void isc_awb_work(struct work_struct *w) mutex_unlock(&isc->awb_mutex); /* if awb has been disabled, we don't need to start another histogram */ - if (ctrls->awb) + if (ctrls->hist_stat == HIST_ENABLED) regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ); pm_runtime_put_sync(isc->dev); -- 2.34.1 From: Balakrishnan Sambath Export raw histogram data to userspace only when all 4 Bayer channels are ready and userspace has active listeners. Also continue AWB work as long as userspace listeners are active. Signed-off-by: Balakrishnan Sambath Signed-off-by: Balamanikandan Gunasundar --- drivers/media/platform/microchip/microchip-isc-base.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index cafd05244db2..6c937a20fce0 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1450,6 +1450,15 @@ static void isc_awb_work(struct work_struct *w) if (hist_id != ISC_HIS_CFG_MODE_B) { hist_id++; } else { + /* All 4 channels processed - notify userspace */ + if (isc_stats_active(&isc->stats)) + isc_stats_isr(&isc->stats); + else + dev_info(isc->dev, "No active userspace listeners\n"); + } + + /* Continue with AWB processing only if AWB is enabled */ + if (ctrls->awb != ISC_WB_NONE) { isc_wb_update(ctrls); hist_id = ISC_HIS_CFG_MODE_GR; } @@ -1499,7 +1508,7 @@ static void isc_awb_work(struct work_struct *w) mutex_lock(&isc->awb_mutex); /* streaming is not active anymore */ - if (isc->stop) { + if (isc->stop && !isc_stats_active(&isc->stats)) { mutex_unlock(&isc->awb_mutex); return; } -- 2.34.1 Add microchip ISC specific statistics meta data format. This data consists of raw histogram statistics that is exported to userspace for further processing. This fixes the kernel warning "Unknown pixelformat" Signed-off-by: Balamanikandan Gunasundar --- drivers/media/v4l2-core/v4l2-ioctl.c | 1 + include/uapi/linux/videodev2.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 01cf52c3ea33..a03e8f3ab610 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1467,6 +1467,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_RK_ISP1_PARAMS: descr = "Rockchip ISP1 3A Parameters"; break; case V4L2_META_FMT_RK_ISP1_STAT_3A: descr = "Rockchip ISP1 3A Statistics"; break; case V4L2_META_FMT_RK_ISP1_EXT_PARAMS: descr = "Rockchip ISP1 Ext 3A Params"; break; + case V4L2_META_FMT_ISC_STAT_3A: descr = "Microchip ISP statistics"; break; case V4L2_META_FMT_C3ISP_PARAMS: descr = "Amlogic C3 ISP Parameters"; break; case V4L2_META_FMT_C3ISP_STATS: descr = "Amlogic C3 ISP Statistics"; break; case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index becd08fdbddb..ba628f9bb89f 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -875,6 +875,9 @@ struct v4l2_pix_format { #define V4L2_META_FMT_RK_ISP1_STAT_3A v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */ #define V4L2_META_FMT_RK_ISP1_EXT_PARAMS v4l2_fourcc('R', 'K', '1', 'E') /* Rockchip ISP1 3a Extensible Parameters */ +/* Vendor specific - used for Microchip camera sub-system */ +#define V4L2_META_FMT_ISC_STAT_3A v4l2_fourcc('I', 'S', 'C', 'S') + /* Vendor specific - used for C3_ISP */ #define V4L2_META_FMT_C3ISP_PARAMS v4l2_fourcc('C', '3', 'P', 'M') /* Amlogic C3 ISP Parameters */ #define V4L2_META_FMT_C3ISP_STATS v4l2_fourcc('C', '3', 'S', 'T') /* Amlogic C3 ISP Statistics */ -- 2.34.1 Enable the Color correction registers as v4l2 controls. Applications such as libcamera can read and write the elements of color correction matrix through the standard v4l2 control API. Signed-off-by: Balamanikandan Gunasundar --- .../platform/microchip/microchip-isc-base.c | 175 +++++++++++++++++- .../media/platform/microchip/microchip-isc.h | 12 ++ include/linux/atmel-isc-media.h | 13 ++ 3 files changed, 199 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index 6c937a20fce0..e679dc02dc9f 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -32,7 +32,7 @@ #include "microchip-isc-regs.h" #include "microchip-isc.h" -#define ISC_IS_FORMAT_RAW(mbus_code) \ +#define ISC_IS_FORMAT_RAW(mbus_code) \ (((mbus_code) & 0xf000) == 0x3000) #define ISC_IS_FORMAT_GREY(mbus_code) \ @@ -1677,6 +1677,165 @@ static int isc_g_volatile_awb_ctrl(struct v4l2_ctrl *ctrl) return 0; } +static int isc_cc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct isc_device *isc = container_of(ctrl->handler, + struct isc_device, ctrls.handler); + struct regmap *regmap = isc->regmap; + + dev_dbg(isc->dev, "id = 0x%x; val = 0x%x", ctrl->id, ctrl->val); + + switch (ctrl->id) { + case ISC_CID_CC_RR: + regmap_update_bits(regmap, ISC_CC_RR_RG, GENMASK(11, 0), ctrl->val); + break; + case ISC_CID_CC_RG: + regmap_update_bits(regmap, ISC_CC_RR_RG, GENMASK(27, 16), + (ctrl->val & GENMASK(11, 0)) << 16); + break; + case ISC_CID_CC_RB: + regmap_update_bits(regmap, ISC_CC_RB_OR, GENMASK(11, 0), ctrl->val); + break; + case ISC_CID_CC_OR: + regmap_update_bits(regmap, ISC_CC_RB_OR, GENMASK(27, 16), + (ctrl->val & GENMASK(11, 0)) << 16); + break; + case ISC_CID_CC_GR: + regmap_update_bits(regmap, ISC_CC_GR_GG, GENMASK(11, 0), ctrl->val); + break; + case ISC_CID_CC_GG: + regmap_update_bits(regmap, ISC_CC_GR_GG, GENMASK(27, 16), + (ctrl->val & GENMASK(11, 0)) << 16); + break; + case ISC_CID_CC_GB: + regmap_update_bits(regmap, ISC_CC_GB_OG, GENMASK(11, 0), ctrl->val); + break; + case ISC_CID_CC_OG: + regmap_update_bits(regmap, ISC_CC_GB_OG, GENMASK(27, 16), + (ctrl->val & GENMASK(11, 0)) << 16); + break; + case ISC_CID_CC_BR: + regmap_update_bits(regmap, ISC_CC_BR_BG, GENMASK(11, 0), ctrl->val); + break; + case ISC_CID_CC_BG: + regmap_update_bits(regmap, ISC_CC_BR_BG, GENMASK(27, 16), + (ctrl->val & GENMASK(11, 0)) << 16); + break; + case ISC_CID_CC_BB: + regmap_update_bits(regmap, ISC_CC_BB_OB, GENMASK(11, 0), ctrl->val); + break; + case ISC_CID_CC_OB: + regmap_update_bits(regmap, ISC_CC_BB_OB, GENMASK(27, 16), + (ctrl->val & GENMASK(11, 0)) << 16); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int isc_cc_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct isc_device *isc = container_of(ctrl->handler, + struct isc_device, ctrls.handler); + struct regmap *regmap = isc->regmap; + unsigned int reg; + + switch (ctrl->id) { + case ISC_CID_CC_RR: + regmap_read(regmap, ISC_CC_RR_RG, ®); + ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11); + break; + case ISC_CID_CC_RG: + regmap_read(regmap, ISC_CC_RR_RG, ®); + ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11); + break; + case ISC_CID_CC_RB: + regmap_read(regmap, ISC_CC_RB_OR, ®); + ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11); + break; + case ISC_CID_CC_OR: + regmap_read(regmap, ISC_CC_RB_OR, ®); + ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11); + break; + case ISC_CID_CC_GR: + regmap_read(regmap, ISC_CC_GR_GG, ®); + ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11); + break; + case ISC_CID_CC_GG: + regmap_read(regmap, ISC_CC_GR_GG, ®); + ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11); + break; + case ISC_CID_CC_GB: + regmap_read(regmap, ISC_CC_GB_OG, ®); + ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11); + break; + case ISC_CID_CC_OG: + regmap_read(regmap, ISC_CC_GB_OG, ®); + ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11); + break; + case ISC_CID_CC_BR: + regmap_read(regmap, ISC_CC_BR_BG, ®); + ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11); + break; + case ISC_CID_CC_BG: + regmap_read(regmap, ISC_CC_BR_BG, ®); + ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11); + break; + case ISC_CID_CC_BB: + regmap_read(regmap, ISC_CC_BB_OB, ®); + ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11); + break; + case ISC_CID_CC_OB: + regmap_read(regmap, ISC_CC_BB_OB, ®); + ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11); + break; + default: + return -EINVAL; + } + + dev_dbg(isc->dev, "id = 0x%x; val = 0x%x", ctrl->id, ctrl->val); + + return 0; +} + +static const struct v4l2_ctrl_ops isc_cc_ops = { + .s_ctrl = isc_cc_s_ctrl, + .g_volatile_ctrl = isc_cc_g_volatile_ctrl, +}; + +#define ISC_CTRL_CC(_name, _id, _name_str) \ + static const struct v4l2_ctrl_config _name = { \ + .ops = &isc_cc_ops, \ + .id = _id, \ + .name = _name_str, \ + .type = V4L2_CTRL_TYPE_INTEGER, \ + .flags = V4L2_CTRL_FLAG_SLIDER, \ + .min = -2048, \ + .max = 2047, \ + .step = 1, \ + .def = 0, \ + } + +ISC_CTRL_CC(isc_cc_rr_ctrl, ISC_CID_CC_RR, "CC RR"); +ISC_CTRL_CC(isc_cc_rg_ctrl, ISC_CID_CC_RG, "CC RG"); + +ISC_CTRL_CC(isc_cc_rb_ctrl, ISC_CID_CC_RB, "CC RB"); +ISC_CTRL_CC(isc_cc_or_ctrl, ISC_CID_CC_OR, "CC OR"); + +ISC_CTRL_CC(isc_cc_gr_ctrl, ISC_CID_CC_GR, "CC GR"); +ISC_CTRL_CC(isc_cc_gg_ctrl, ISC_CID_CC_GG, "CC GG"); + +ISC_CTRL_CC(isc_cc_gb_ctrl, ISC_CID_CC_GB, "CC GB"); +ISC_CTRL_CC(isc_cc_og_ctrl, ISC_CID_CC_OG, "CC OG"); + +ISC_CTRL_CC(isc_cc_br_ctrl, ISC_CID_CC_BR, "CC BR"); +ISC_CTRL_CC(isc_cc_bg_ctrl, ISC_CID_CC_BG, "CC BG"); + +ISC_CTRL_CC(isc_cc_bb_ctrl, ISC_CID_CC_BB, "CC BB"); +ISC_CTRL_CC(isc_cc_ob_ctrl, ISC_CID_CC_OB, "CC OB"); + static const struct v4l2_ctrl_ops isc_awb_ops = { .s_ctrl = isc_s_awb_ctrl, .g_volatile_ctrl = isc_g_volatile_awb_ctrl, @@ -1767,6 +1926,20 @@ static int isc_ctrl_init(struct isc_device *isc) isc->gr_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gr_off_ctrl, NULL); isc->gb_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gb_off_ctrl, NULL); + /* Color correction control */ + isc->cc_rr = v4l2_ctrl_new_custom(hdl, &isc_cc_rr_ctrl, NULL); + isc->cc_rg = v4l2_ctrl_new_custom(hdl, &isc_cc_rg_ctrl, NULL); + isc->cc_rb = v4l2_ctrl_new_custom(hdl, &isc_cc_rb_ctrl, NULL); + isc->cc_or = v4l2_ctrl_new_custom(hdl, &isc_cc_or_ctrl, NULL); + isc->cc_gr = v4l2_ctrl_new_custom(hdl, &isc_cc_gr_ctrl, NULL); + isc->cc_gg = v4l2_ctrl_new_custom(hdl, &isc_cc_gg_ctrl, NULL); + isc->cc_gb = v4l2_ctrl_new_custom(hdl, &isc_cc_gb_ctrl, NULL); + isc->cc_og = v4l2_ctrl_new_custom(hdl, &isc_cc_og_ctrl, NULL); + isc->cc_br = v4l2_ctrl_new_custom(hdl, &isc_cc_br_ctrl, NULL); + isc->cc_bg = v4l2_ctrl_new_custom(hdl, &isc_cc_bg_ctrl, NULL); + isc->cc_bb = v4l2_ctrl_new_custom(hdl, &isc_cc_bb_ctrl, NULL); + isc->cc_ob = v4l2_ctrl_new_custom(hdl, &isc_cc_ob_ctrl, NULL); + /* * The cluster is in auto mode with autowhitebalance enabled * and manual mode otherwise. diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h index fcb20669ef69..aaa6c4b653d4 100644 --- a/drivers/media/platform/microchip/microchip-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -357,6 +357,18 @@ struct isc_device { struct v4l2_ctrl *b_off_ctrl; struct v4l2_ctrl *gr_off_ctrl; struct v4l2_ctrl *gb_off_ctrl; + struct v4l2_ctrl *cc_rr; + struct v4l2_ctrl *cc_rg; + struct v4l2_ctrl *cc_rb; + struct v4l2_ctrl *cc_or; + struct v4l2_ctrl *cc_gr; + struct v4l2_ctrl *cc_gg; + struct v4l2_ctrl *cc_gb; + struct v4l2_ctrl *cc_og; + struct v4l2_ctrl *cc_br; + struct v4l2_ctrl *cc_bg; + struct v4l2_ctrl *cc_bb; + struct v4l2_ctrl *cc_ob; }; /* Statistics device */ diff --git a/include/linux/atmel-isc-media.h b/include/linux/atmel-isc-media.h index 79a320fb724e..028d34c8de81 100644 --- a/include/linux/atmel-isc-media.h +++ b/include/linux/atmel-isc-media.h @@ -53,6 +53,19 @@ enum atmel_isc_ctrl_id { ISC_CID_GR_OFFSET, /* Green Blue component offset control */ ISC_CID_GB_OFFSET, + /* Color correction registers */ + ISC_CID_CC_RR, + ISC_CID_CC_RG, + ISC_CID_CC_RB, + ISC_CID_CC_OR, + ISC_CID_CC_GR, + ISC_CID_CC_GG, + ISC_CID_CC_GB, + ISC_CID_CC_OG, + ISC_CID_CC_BR, + ISC_CID_CC_BG, + ISC_CID_CC_BB, + ISC_CID_CC_OB, }; #endif -- 2.34.1