Add V4L2 controls (EXPOSURE/GAIN/VBLANK/HBLANK/PIXEL_RATE/LINK_FREQ) and libcamera support

- Add mandatory V4L2 controls required by libcamera:
  PIXEL_RATE, LINK_FREQ, EXPOSURE, ANALOGUE_GAIN, VBLANK, HBLANK
- Add V4L2_SUBDEV_FL_HAS_DEVNODE flag to create /dev/v4l-subdev0
- Rename driver .name to 'ov5647' to use existing IPA cam_helper
- Add sc235hai_set_exposure/gain/vts() register write functions
- Add sc235hai_init_controls() and sc235hai_s_ctrl() ctrl ops
- Register exposure (3e00/01/02), gain (3e08/09), VTS (320e/0f)

Result: libcamera v0.6.0 recognizes camera, mediamtx RTSP stream
working at rtsp://[board]:8554/cam with ISP processing
This commit is contained in:
yuquanjun 2026-04-19 14:17:12 +08:00
parent 55dfae8c2c
commit c32fd1b233
1 changed files with 159 additions and 2 deletions

View File

@ -31,11 +31,34 @@
/* Chip ID */
#define SC235HAI_REG_LOW_POWER 0x302c
/* Exposure registers: {3e00[3:0], 3e01[7:0], 3e02[7:4]} in half-row units */
#define SC235HAI_REG_EXP_H 0x3e00
#define SC235HAI_REG_EXP_M 0x3e01
#define SC235HAI_REG_EXP_L 0x3e02
/* Gain registers */
#define SC235HAI_REG_GAIN_H 0x3e08 /* coarse analog gain */
#define SC235HAI_REG_GAIN_L 0x3e09 /* fine analog gain */
/* Vertical timing size register */
#define SC235HAI_REG_VTS_H 0x320e
#define SC235HAI_REG_VTS_L 0x320f
/* Pixel rate: HTS * VTS * FPS = 2200 * 1125 * 30 = 74,250,000 Hz */
#define SC235HAI_PIXEL_RATE 74250000UL
/* MIPI link frequency: pixel_rate * bpp / (2 * lanes) = 74.25M * 10 / 4 */
#define SC235HAI_LINK_FREQ 185625000LL
/* Default initial values */
#define SC235HAI_DEFAULT_WIDTH 1920
#define SC235HAI_DEFAULT_HEIGHT 1080
#define SC235HAI_DEFAULT_FPS 30
/* Gain limits */
#define SC235HAI_GAIN_MIN 0x80
#define SC235HAI_GAIN_MAX 0x7fc0
#define SC235HAI_GAIN_DEF 0x80
#define SC235HAI_GAIN_STEP 1
struct sc235hai_mode {
u32 width;
u32 height;
@ -63,11 +86,16 @@ struct sc235hai {
const struct sc235hai_mode *cur_mode;
struct v4l2_ctrl *exposure;
struct v4l2_ctrl *gain;
struct v4l2_ctrl *vblank;
struct mutex lock;
bool streaming;
};
static const s64 sc235hai_link_freq_menu[] = {
SC235HAI_LINK_FREQ,
};
static inline struct sc235hai *to_sc235hai(struct v4l2_subdev *sd)
{
return container_of(sd, struct sc235hai, sd);
@ -468,6 +496,125 @@ static const struct v4l2_subdev_ops sc235hai_subdev_ops = {
.pad = &sc235hai_pad_ops,
};
static int sc235hai_set_exposure(struct sc235hai *sc235hai, u32 exp_lines)
{
/* Exposure in half-row units: val = exp_lines * 2 */
u32 val = exp_lines * 2;
sc235hai_write_reg(sc235hai, SC235HAI_REG_EXP_H, (val >> 12) & 0x0f);
sc235hai_write_reg(sc235hai, SC235HAI_REG_EXP_M, (val >> 4) & 0xff);
return sc235hai_write_reg(sc235hai, SC235HAI_REG_EXP_L,
(val & 0x0f) << 4);
}
static int sc235hai_set_gain(struct sc235hai *sc235hai, u32 gain)
{
/* gain: upper 7 bits → 0x3e08 coarse; lower 8 bits → 0x3e09 fine */
u8 coarse = (gain >> 8) & 0x7f;
u8 fine = gain & 0xff;
sc235hai_write_reg(sc235hai, SC235HAI_REG_GAIN_H, coarse);
return sc235hai_write_reg(sc235hai, SC235HAI_REG_GAIN_L, fine);
}
static int sc235hai_set_vts(struct sc235hai *sc235hai, u32 vblank)
{
u32 vts = vblank + sc235hai->cur_mode->height;
sc235hai_write_reg(sc235hai, SC235HAI_REG_VTS_H, (vts >> 8) & 0xff);
return sc235hai_write_reg(sc235hai, SC235HAI_REG_VTS_L, vts & 0xff);
}
static int sc235hai_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct sc235hai *sc235hai =
container_of(ctrl->handler, struct sc235hai, ctrl_handler);
int ret = 0;
if (pm_runtime_get_if_in_use(sc235hai->sd.dev) == 0)
return 0;
switch (ctrl->id) {
case V4L2_CID_EXPOSURE:
ret = sc235hai_set_exposure(sc235hai, ctrl->val);
break;
case V4L2_CID_ANALOGUE_GAIN:
ret = sc235hai_set_gain(sc235hai, ctrl->val);
break;
case V4L2_CID_VBLANK:
ret = sc235hai_set_vts(sc235hai, ctrl->val);
break;
default:
break;
}
pm_runtime_put(sc235hai->sd.dev);
return ret;
}
static const struct v4l2_ctrl_ops sc235hai_ctrl_ops = {
.s_ctrl = sc235hai_s_ctrl,
};
static int sc235hai_init_controls(struct sc235hai *sc235hai)
{
struct v4l2_ctrl_handler *hdl = &sc235hai->ctrl_handler;
const struct sc235hai_mode *mode = sc235hai->cur_mode;
s64 vblank_def = mode->vts - mode->height;
s64 vblank_max = 0x7fff - mode->height;
int ret;
v4l2_ctrl_handler_init(hdl, 7);
/* Pixel rate — read-only */
v4l2_ctrl_new_std(hdl, &sc235hai_ctrl_ops,
V4L2_CID_PIXEL_RATE,
SC235HAI_PIXEL_RATE, SC235HAI_PIXEL_RATE, 1,
SC235HAI_PIXEL_RATE);
/* Link frequency — read-only */
v4l2_ctrl_new_int_menu(hdl, &sc235hai_ctrl_ops,
V4L2_CID_LINK_FREQ,
ARRAY_SIZE(sc235hai_link_freq_menu) - 1, 0,
sc235hai_link_freq_menu);
/* Exposure — in whole lines */
sc235hai->exposure = v4l2_ctrl_new_std(hdl, &sc235hai_ctrl_ops,
V4L2_CID_EXPOSURE,
4, mode->vts - 4, 1,
mode->vts / 2);
/* Analogue gain — raw register composite value */
sc235hai->gain = v4l2_ctrl_new_std(hdl, &sc235hai_ctrl_ops,
V4L2_CID_ANALOGUE_GAIN,
SC235HAI_GAIN_MIN,
SC235HAI_GAIN_MAX,
SC235HAI_GAIN_STEP,
SC235HAI_GAIN_DEF);
/* Vertical blanking */
sc235hai->vblank = v4l2_ctrl_new_std(hdl, &sc235hai_ctrl_ops,
V4L2_CID_VBLANK,
vblank_def, vblank_max, 1,
vblank_def);
/* Horizontal blanking — read-only */
v4l2_ctrl_new_std(hdl, &sc235hai_ctrl_ops,
V4L2_CID_HBLANK,
mode->hts - mode->width,
mode->hts - mode->width, 1,
mode->hts - mode->width);
if (hdl->error) {
ret = hdl->error;
v4l2_ctrl_handler_free(hdl);
return ret;
}
sc235hai->sd.ctrl_handler = hdl;
return 0;
}
static int sc235hai_power_on(struct device *dev)
{
struct v4l2_subdev *sd = dev_get_drvdata(dev);
@ -521,6 +668,7 @@ static int sc235hai_probe(struct i2c_client *client)
return -ENOMEM;
v4l2_i2c_subdev_init(&sc235hai->sd, client, &sc235hai_subdev_ops);
sc235hai->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
ret = sc235hai_check_hwcfg(dev);
if (ret) {
@ -589,13 +737,20 @@ static int sc235hai_probe(struct i2c_client *client)
/* Set default mode */
sc235hai->cur_mode = &sc235hai_modes[0];
/* Initialize V4L2 controls */
ret = sc235hai_init_controls(sc235hai);
if (ret) {
dev_err(dev, "Failed to init controls: %d\n", ret);
goto error_power_off;
}
/* Initialize media entity */
sc235hai->pad.flags = MEDIA_PAD_FL_SOURCE;
sc235hai->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
ret = media_entity_pads_init(&sc235hai->sd.entity, 1, &sc235hai->pad);
if (ret) {
dev_err(dev, "Failed to init entity pads: %d\n", ret);
goto error_power_off;
goto error_ctrl;
}
ret = v4l2_async_register_subdev(&sc235hai->sd);
@ -612,6 +767,8 @@ static int sc235hai_probe(struct i2c_client *client)
error_media:
media_entity_cleanup(&sc235hai->sd.entity);
error_ctrl:
v4l2_ctrl_handler_free(&sc235hai->ctrl_handler);
error_power_off:
sc235hai_power_off(dev);
error_mutex:
@ -648,7 +805,7 @@ static const struct dev_pm_ops sc235hai_pm_ops = {
static struct i2c_driver sc235hai_i2c_driver = {
.driver = {
.name = "sc235hai",
.name = "ov5647",
.of_match_table = sc235hai_of_match,
.pm = &sc235hai_pm_ops,
},