Re: [PATCH v7] s5k5baf: add camera sensor driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Andrzej,

Thank you for the patch.

On Wednesday 21 August 2013 16:41:31 Andrzej Hajda wrote:
> Driver for Samsung S5K5BAF UXGA 1/5" 2M CMOS Image Sensor
> with embedded SoC ISP.
> The driver exposes the sensor as two V4L2 subdevices:
> - S5K5BAF-CIS - pure CMOS Image Sensor, fixed 1600x1200 format,
>   no controls.
> - S5K5BAF-ISP - Image Signal Processor, formats up to 1600x1200,
>   pre/post ISP cropping, downscaling via selection API, controls.
> 
> Signed-off-by: Sylwester Nawrocki <s.nawrocki@xxxxxxxxxxx>
> Signed-off-by: Andrzej Hajda <a.hajda@xxxxxxxxxxx>
> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx>
> ---
> Hi,
> 
> This patch incorporates Stephen's suggestions, thanks.
> 
> Regards
> Andrzej
> 
> v7
> - changed description of 'clock-frequency' DT property
> 
> v6
> - endpoint node presence is now optional,
> - added asynchronous subdev registration support and clock
>   handling,
> - use named gpios in DT bindings
> 
> v5
> - removed hflip/vflip device tree properties
> 
> v4
> - GPL changed to GPLv2,
> - bitfields replaced by u8,
> - cosmetic changes,
> - corrected s_stream flow,
> - gpio pins are no longer exported,
> - added I2C addresses to subdev names,
> - CIS subdev registration postponed after
>   succesfull HW initialization,
> - added enums for pads,
> - selections are initialized only during probe,
> - default resolution changed to 1600x1200,
> - state->error pattern removed from few other functions,
> - entity link creation moved to registered callback.
> 
> v3:
> - narrowed state->error usage to i2c and power errors,
> - private gain controls replaced by red/blue balance user controls,
> - added checks to devicetree gpio node parsing
> 
> v2:
> - lower-cased driver name,
> - removed underscore from regulator names,
> - removed platform data code,
> - v4l controls grouped in anonymous structs,
> - added s5k5baf_clear_error function,
> - private controls definitions moved to uapi header file,
> - added v4l2-controls.h reservation for private controls,
> - corrected subdev registered/unregistered code,
> - .log_status sudbev op set to v4l2 helper,
> - moved entity link creation to probe routines,
> - added cleanup on error to probe function.
> ---
>  .../devicetree/bindings/media/samsung-s5k5baf.txt  |   59 +
>  MAINTAINERS                                        |    7 +
>  drivers/media/i2c/Kconfig                          |    7 +
>  drivers/media/i2c/Makefile                         |    1 +
>  drivers/media/i2c/s5k5baf.c                        | 2045 +++++++++++++++++
>  5 files changed, 2119 insertions(+)
>  create mode 100644
> Documentation/devicetree/bindings/media/samsung-s5k5baf.txt create mode
> 100644 drivers/media/i2c/s5k5baf.c

[snip]

> diff --git a/drivers/media/i2c/s5k5baf.c b/drivers/media/i2c/s5k5baf.c
> new file mode 100644
> index 0000000..f21d9f1
> --- /dev/null
> +++ b/drivers/media/i2c/s5k5baf.c

[snip]

> +enum s5k5baf_pads_id {
> +	PAD_CIS,
> +	PAD_OUT,
> +	CIS_PAD_NUM = 1,
> +	ISP_PAD_NUM = 2
> +};

You can just use #define's here, the enum doesn't bring any additional value 
and isn't very explicit.

> +static struct v4l2_rect s5k5baf_cis_rect = { 0, 0, S5K5BAF_CIS_WIDTH,
> +				     S5K5BAF_CIS_HEIGHT };

Shouldn't this be const ?

> +static u16 s5k5baf_i2c_read(struct s5k5baf *state, u16 addr)
> +{
> +	struct i2c_client *c = v4l2_get_subdevdata(&state->sd);
> +	u16 w, r;

You should declare these variables as __be16.

> +	struct i2c_msg msg[] = {
> +		{ .addr = c->addr, .flags = 0,
> +		  .len = 2, .buf = (u8 *)&w },
> +		{ .addr = c->addr, .flags = I2C_M_RD,
> +		  .len = 2, .buf = (u8 *)&r },
> +	};
> +	int ret;
> +
> +	if (state->error)
> +		return 0;
> +
> +	w = htons(addr);

Wouldln't cpu_to_be16() be more appropriate ?

> +	ret = i2c_transfer(c->adapter, msg, 2);
> +	r = ntohs(r);

And be16_to_cpu() here.

> +
> +	v4l2_dbg(3, debug, c, "i2c_read: 0x%04x : 0x%04x\n", addr, r);
> +
> +	if (ret != 2) {
> +		v4l2_err(c, "i2c_read: error during transfer (%d)\n", ret);
> +		state->error = ret;
> +	}
> +	return r;
> +}

[snip]

> +static void s5k5baf_write_arr_seq(struct s5k5baf *state, u16 addr,
> +				  u16 count, const u16 *seq)
> +{
> +	struct i2c_client *c = v4l2_get_subdevdata(&state->sd);
> +	u16 buf[count + 1];
> +	int ret, n;
> +
> +	s5k5baf_i2c_write(state, REG_CMDWR_ADDR, addr);
> +	if (state->error)
> +		return;

I would have a preference for returning an error directly from the write 
function instead of storing it in state->error, that would be more explicit. 
The same is true for all read/write functions.

> +	buf[0] = __constant_htons(REG_CMD_BUF);
> +	for (n = 1; n <= count; ++n)
> +		buf[n] = htons(*seq++);

cpu_to_be16()/be16_to_cpu() here as well ?

> +
> +	n *= 2;
> +	ret = i2c_master_send(c, (char *)buf, n);
> +	v4l2_dbg(3, debug, c, "i2c_write_seq(count=%d): %*ph\n", count,
> +		 min(2 * count, 64), seq - count);
> +
> +	if (ret != n) {
> +		v4l2_err(c, "i2c_write_seq: error during transfer (%d)\n", ret);
> +		state->error = ret;
> +	}
> +}

[snip]

> +static void s5k5baf_hw_set_ccm(struct s5k5baf *state)

A small comment explaining what this function configures would be nice.

> +{
> +	static const u16 nseq_cfg[] = {
> +		NSEQ(REG_PTR_CCM_HORIZON,
> +		REG_ARR_CCM(0), PAGE_IF_SW,
> +		REG_ARR_CCM(1), PAGE_IF_SW,
> +		REG_ARR_CCM(2), PAGE_IF_SW,
> +		REG_ARR_CCM(3), PAGE_IF_SW,
> +		REG_ARR_CCM(4), PAGE_IF_SW,
> +		REG_ARR_CCM(5), PAGE_IF_SW),
> +		NSEQ(REG_PTR_CCM_OUTDOOR,
> +		REG_ARR_CCM(6), PAGE_IF_SW),
> +		NSEQ(REG_ARR_CCM(0),
> +		/* horizon */
> +		0x010d, 0xffa7, 0xfff5, 0x003b, 0x00ef, 0xff38,
> +		0xfe42, 0x0270, 0xff71, 0xfeed, 0x0198, 0x0198,
> +		0xff95, 0xffa3, 0x0260, 0x00ec, 0xff33, 0x00f4,
> +		/* incandescent */
> +		0x010d, 0xffa7, 0xfff5, 0x003b, 0x00ef, 0xff38,
> +		0xfe42, 0x0270, 0xff71, 0xfeed, 0x0198, 0x0198,
> +		0xff95, 0xffa3, 0x0260, 0x00ec, 0xff33, 0x00f4,
> +		/* warm white */
> +		0x01ea, 0xffb9, 0xffdb, 0x0127, 0x0109, 0xff3c,
> +		0xff2b, 0x021b, 0xff48, 0xff03, 0x0207, 0x0113,
> +		0xffca, 0xff93, 0x016f, 0x0164, 0xff55, 0x0163,
> +		/* cool white */
> +		0x01ea, 0xffb9, 0xffdb, 0x0127, 0x0109, 0xff3c,
> +		0xff2b, 0x021b, 0xff48, 0xff03, 0x0207, 0x0113,
> +		0xffca, 0xff93, 0x016f, 0x0164, 0xff55, 0x0163,
> +		/* daylight 5000K */
> +		0x0194, 0xffad, 0xfffe, 0x00c5, 0x0103, 0xff5d,
> +		0xfee3, 0x01ae, 0xff27, 0xff18, 0x018f, 0x00c8,
> +		0xffe8, 0xffaa, 0x01c8, 0x0132, 0xff3e, 0x0100,
> +		/* daylight 6500K */
> +		0x0194, 0xffad, 0xfffe, 0x00c5, 0x0103, 0xff5d,
> +		0xfee3, 0x01ae, 0xff27, 0xff18, 0x018f, 0x00c8,
> +		0xffe8, 0xffaa, 0x01c8, 0x0132, 0xff3e, 0x0100,
> +		/* outdoor */
> +		0x01cc, 0xffc3, 0x0009, 0x00a2, 0x0106, 0xff3f,
> +		0xfed8, 0x01fe, 0xff08, 0xfec7, 0x00f5, 0x0119,
> +		0xffdf, 0x0024, 0x01a8, 0x0170, 0xffad, 0x011b),
> +		0
> +	};
> +	s5k5baf_write_nseq(state, nseq_cfg);
> +}
> +
> +static void s5k5baf_hw_set_cis(struct s5k5baf *state)

Same here.

> +{
> +	static const u16 nseq_cfg[] = {
> +		NSEQ(0xc202, 0x0700),
> +		NSEQ(0xf260, 0x0001),
> +		NSEQ(0xf414, 0x0030),
> +		NSEQ(0xc204, 0x0100),
> +		NSEQ(0xf402, 0x0092, 0x007f),
> +		NSEQ(0xf700, 0x0040),
> +		NSEQ(0xf708,
> +		0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
> +		0x0040, 0x0040, 0x0040, 0x0040, 0x0040,
> +		0x0001, 0x0015, 0x0001, 0x0040),
> +		NSEQ(0xf48a, 0x0048),
> +		NSEQ(0xf10a, 0x008b),
> +		NSEQ(0xf900, 0x0067),
> +		NSEQ(0xf406, 0x0092, 0x007f, 0x0003, 0x0003, 0x0003),
> +		NSEQ(0xf442, 0x0000, 0x0000),
> +		NSEQ(0xf448, 0x0000),
> +		NSEQ(0xf456, 0x0001, 0x0010, 0x0000),
> +		NSEQ(0xf41a, 0x00ff, 0x0003, 0x0030),
> +		NSEQ(0xf410, 0x0001, 0x0000),
> +		NSEQ(0xf416, 0x0001),
> +		NSEQ(0xf424, 0x0000),
> +		NSEQ(0xf422, 0x0000),
> +		NSEQ(0xf41e, 0x0000),
> +		NSEQ(0xf428, 0x0000, 0x0000, 0x0000),
> +		NSEQ(0xf430, 0x0000, 0x0000, 0x0008, 0x0005, 0x000f, 0x0001,
> +		0x0040, 0x0040, 0x0010),
> +		NSEQ(0xf4d6, 0x0090, 0x0000),
> +		NSEQ(0xf47c, 0x000c, 0x0000),
> +		NSEQ(0xf49a, 0x0008, 0x0000),
> +		NSEQ(0xf4a2, 0x0008, 0x0000),
> +		NSEQ(0xf4b2, 0x0013, 0x0000, 0x0013, 0x0000),
> +		NSEQ(0xf4aa, 0x009b, 0x00fb, 0x009b, 0x00fb),
> +		0
> +	};
> +
> +	s5k5baf_i2c_write(state, REG_CMDWR_PAGE, PAGE_IF_HW);
> +	s5k5baf_write_nseq(state, nseq_cfg);
> +	s5k5baf_i2c_write(state, REG_CMDWR_PAGE, PAGE_IF_SW);
> +}

[snip]

> +/* Set auto/manual exposure and total gain */
> +static void s5k5baf_hw_set_auto_exposure(struct s5k5baf *state, int value)
> +{
> +	unsigned int exp_time = state->ctrls.exposure->val;
> +	u16 auto_alg;
> +
> +	auto_alg = s5k5baf_read(state, REG_DBG_AUTOALG_EN);

Wouldn't it be faster to cache the register value in the state structure 
instead of reading it back ? By the way, you might want to have a look at 
regmap.

> +
> +	if (value == V4L2_EXPOSURE_AUTO) {
> +		auto_alg |= AALG_AE_EN | AALG_DIVLEI_EN;
> +	} else {
> +		s5k5baf_hw_set_user_exposure(state, exp_time);
> +		s5k5baf_hw_set_user_gain(state, state->ctrls.gain->val);
> +		auto_alg &= ~(AALG_AE_EN | AALG_DIVLEI_EN);
> +	}
> +
> +	s5k5baf_write(state, REG_DBG_AUTOALG_EN, auto_alg);
> +}

> +static void s5k5baf_hw_set_colorfx(struct s5k5baf *state, int val)
> +{
> +	static const u16 colorfx[] = {
> +		[V4L2_COLORFX_NONE] = 0,
> +		[V4L2_COLORFX_BW] = 1,
> +		[V4L2_COLORFX_NEGATIVE] = 2,
> +		[V4L2_COLORFX_SEPIA] = 3,
> +		[V4L2_COLORFX_SKY_BLUE] = 4,
> +		[V4L2_COLORFX_SKETCH] = 5,
> +	};
> +
> +	if (val >= ARRAY_SIZE(colorfx)) {
> +		v4l2_err(&state->sd, "colorfx(%d) out of range(%d)\n",
> +			 val, ARRAY_SIZE(colorfx));
> +		state->error = -EINVAL;

Can this happen, given the range of admissible values for the control ?

> +	} else {
> +		s5k5baf_write(state, REG_G_SPEC_EFFECTS, colorfx[val]);
> +	}
> +}
> +
> +static int s5k5baf_find_pixfmt(struct v4l2_mbus_framefmt *mf)
> +{
> +	int i, c = -1;

I tend to use unsigned int for integer variables that store unsigned content 
(i in this case), but that might just be me.

> +
> +	for (i = 0; i < ARRAY_SIZE(s5k5baf_formats); i++) {
> +		if (mf->colorspace != s5k5baf_formats[i].colorspace)
> +			continue;
> +		if (mf->code == s5k5baf_formats[i].code)
> +			return i;
> +		if (c < 0)
> +			c = i;
> +	}
> +	return (c < 0) ? 0 : c;
> +}

[snip]

> +static int s5k5baf_hw_set_video_bus(struct s5k5baf *state)
> +{
> +	u16 en_packets;
> +
> +	switch (state->bus_type) {
> +	case V4L2_MBUS_CSI2:
> +		en_packets = EN_PACKETS_CSI2;
> +		break;
> +	case V4L2_MBUS_PARALLEL:
> +		en_packets = 0;
> +		break;
> +	default:
> +		v4l2_err(&state->sd, "unknown video bus: %d\n",
> +			 state->bus_type);
> +		return -EINVAL;

Can this happen ?

> +	};
> +
> +	s5k5baf_write_seq(state, REG_OIF_EN_MIPI_LANES,
> +			  state->nlanes, en_packets, 1);
> +
> +	return s5k5baf_clear_error(state);
> +}

[snip]

> +static int s5k5baf_s_stream(struct v4l2_subdev *sd, int on)
> +{
> +	struct s5k5baf *state = to_s5k5baf(sd);
> +	int ret;
> +
> +	if (state->streaming == !!on)
> +		return 0;
> +
> +	mutex_lock(&state->lock);

Shouldn't the lock protect the state->streaming check above ?

> +	if (on) {
> +		s5k5baf_hw_set_config(state);
> +		ret = s5k5baf_hw_set_crop_rects(state);
> +		if (ret < 0)
> +			goto out;
> +		s5k5baf_hw_set_stream(state, 1);
> +		s5k5baf_i2c_write(state, 0xb0cc, 0x000b);
> +	} else {
> +		s5k5baf_hw_set_stream(state, 0);
> +	}
> +	ret = s5k5baf_clear_error(state);
> +	if (!ret)
> +		state->streaming = !state->streaming;
> +
> +out:
> +	mutex_unlock(&state->lock);
> +
> +	return ret;
> +}

[snip]

> +/*
> + * V4L2 subdev internal operations
> + */
> +static int s5k5baf_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> +{
> +	struct v4l2_mbus_framefmt *mf;
> +
> +	mf = v4l2_subdev_get_try_format(fh, PAD_CIS);
> +	s5k5baf_try_cis_format(mf);
> +
> +	if (s5k5baf_is_cis_subdev(sd))
> +		return 0;

What about defining two open operations instead, one for each subdev ?

> +	mf = v4l2_subdev_get_try_format(fh, PAD_OUT);
> +	mf->colorspace = s5k5baf_formats[0].colorspace;
> +	mf->code = s5k5baf_formats[0].code;
> +	mf->width = s5k5baf_cis_rect.width;
> +	mf->height = s5k5baf_cis_rect.height;
> +	mf->field = V4L2_FIELD_NONE;
> +
> +	*v4l2_subdev_get_try_crop(fh, PAD_CIS) = s5k5baf_cis_rect;
> +	*v4l2_subdev_get_try_compose(fh, PAD_CIS) = s5k5baf_cis_rect;
> +	*v4l2_subdev_get_try_crop(fh, PAD_OUT) = s5k5baf_cis_rect;
> +
> +	return 0;
> +}
> +
> +static int s5k5baf_check_fw_revision(struct s5k5baf *state)
> +{
> +	u16 api_ver = 0, fw_rev = 0, s_id = 0;
> +	int ret;
> +
> +	api_ver = s5k5baf_read(state, REG_FW_APIVER);
> +	fw_rev = s5k5baf_read(state, REG_FW_REVISION) & 0xff;
> +	s_id = s5k5baf_read(state, REG_FW_SENSOR_ID);
> +	ret = s5k5baf_clear_error(state);
> +	if (ret < 0)
> +		return ret;
> +
> +	v4l2_info(&state->sd, "FW API=%#x, revision=%#x sensor_id=%#x\n",
> +		  api_ver, fw_rev, s_id);
> +
> +	if (api_ver == S5K5BAF_FW_APIVER)
> +		return 0;

I would have dealt with the error inside the if, but that's up to you.

> +	v4l2_err(&state->sd, "FW API version not supported\n");
> +	return -ENODEV;
> +}
> +
> +static int s5k5baf_registered(struct v4l2_subdev *sd)
> +{
> +	struct s5k5baf *state = to_s5k5baf(sd);
> +	int ret;
> +
> +	ret = v4l2_device_register_subdev(sd->v4l2_dev, &state->cis_sd);
> +	if (ret < 0)
> +		v4l2_err(sd, "failed to register subdev %s\n",
> +			 state->cis_sd.name);
> +	else
> +		ret = media_entity_create_link(&state->cis_sd.entity, PAD_CIS,
> +					       &state->sd.entity, PAD_CIS,
> +					       MEDIA_LNK_FL_IMMUTABLE |
> +					       MEDIA_LNK_FL_ENABLED);
> +	return ret;
> +}
> +
> +static void s5k5baf_unregistered(struct v4l2_subdev *sd)
> +{
> +	struct s5k5baf *state = to_s5k5baf(sd);
> +	v4l2_device_unregister_subdev(&state->cis_sd);

The unregistered operation is called from v4l2_device_unregister_subdev(). 
Calling it again will be a no-op, the function will return immediately. You 
can thus get rid of the unregistered operation completely.

Similarly, the registered operation is called from 
v4l2_device_register_subdev(). You can get rid of it as well and just create 
the link in the probe function.

> +}

[snip]

> +static int s5k5baf_parse_device_node(struct s5k5baf *state, struct device
> *dev) +{
> +	struct device_node *node = dev->of_node;
> +	struct device_node *node_ep;
> +	struct v4l2_of_endpoint ep;
> +	int ret;
> +
> +	if (!node) {
> +		dev_err(dev, "no device-tree node provided\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = of_property_read_u32(node, "clock-frequency",
> +				   &state->mclk_frequency);
> +	if (ret < 0) {
> +		state->mclk_frequency = S5K5BAF_DEFAULT_MCLK_FREQ;
> +		dev_info(dev, "using default %u Hz clock frequency\n",
> +			 state->mclk_frequency);
> +	}
> +
> +	ret = s5k5baf_parse_gpios(state->gpios, dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	node_ep = v4l2_of_get_next_endpoint(node, NULL);
> +	if (!node_ep) {
> +		/* Default data bus configuration: MIPI CSI-2, 1 data lane. */
> +		state->bus_type = V4L2_MBUS_CSI2;
> +		state->nlanes = S5K5BAF_DEF_NUM_LANES;
> +		dev_warn(dev, "no endpoint defined at node %s\n",
> +			 node->full_name);
> +		return 0;

Shouldn't this be a fatal error ? If there's no endpoint the sensor isn't part 
of any media pipeline, so it's unusable anyway.

> +	}
> +
> +	v4l2_of_parse_endpoint(node_ep, &ep);
> +	of_node_put(node_ep);
> +	state->bus_type = ep.bus_type;
> +
> +	if (state->bus_type == V4L2_MBUS_CSI2)
> +		state->nlanes = ep.bus.mipi_csi2.num_data_lanes;
> +
> +	return 0;
> +}

-- 
Regards,

Laurent Pinchart

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux