Re: [PATCH v4] media: spi: Add support for LMH0395

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

 




Hi Laurent,

Thanks for the review.

2014-11-03 14:13 GMT+01:00 Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>:
> Hi Jean-Michel,
>
> Thank you for the patch.
>
> On Wednesday 10 September 2014 11:43:54 Jean-Michel Hautbois wrote:
>> This device is a SPI based device from TI.
>> It is a 3 Gbps HD/SD SDI Dual Output Low Power
>> Extended Reach Adaptive Cable Equalizer.
>>
>> LMH0395 enables the use of up to two outputs.
>> These can be configured using DT.
>>
>> Controls should be accessible from userspace too.
>> This will have to be done later.
>>
>> v2: Add DT support
>> v3: Change the bit set/clear in output_type as disabled means 'set the bit'
>> v4: Clearer description of endpoints usage in Doc, and some init changes.
>>     Add a dependency on OF and don't test CONFIG_OF anymore.
>>
>> Signed-off-by: Jean-Michel Hautbois <jean-michel.hautbois@xxxxxxxxxxx>
>> ---
>>  .../devicetree/bindings/media/spi/lmh0395.txt      |  48 +++
>>  MAINTAINERS                                        |   6 +
>>  drivers/media/spi/Kconfig                          |  14 +
>>  drivers/media/spi/Makefile                         |   1 +
>>  drivers/media/spi/lmh0395.c                        | 354 ++++++++++++++++++
>>  5 files changed, 423 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/media/spi/lmh0395.txt
>>  create mode 100644 drivers/media/spi/Kconfig
>>  create mode 100644 drivers/media/spi/Makefile
>>  create mode 100644 drivers/media/spi/lmh0395.c
>>
>> diff --git a/Documentation/devicetree/bindings/media/spi/lmh0395.txt
>> b/Documentation/devicetree/bindings/media/spi/lmh0395.txt new file mode
>> 100644
>> index 0000000..7cc0026
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/spi/lmh0395.txt
>> @@ -0,0 +1,48 @@
>> +* Texas Instruments lmh0395 3G HD/SD SDI equalizer
>> +
>> +The LMH0395 3 Gbps HD/SD SDI Dual Output Low Power Extended Reach Adaptive
>> +Cable Equalizer is designed to equalize data transmitted over cable (or any
>> +media with similar dispersive loss characteristics).
>> +The equalizer operates over a wide range of data rates from 125 Mbps to
>> 2.97 Gbps
>> +and supports SMPTE 424M, SMPTE 292M, SMPTE344M, SMPTE 259M, and DVB-ASI
>> standards.
>
> That's copied verbatim from the datasheet and not very relevant from a DT
> bindings point of view. I would rather explain what the chip does. Something
> like
>
> "The LMH0395 is an SDI equalizer designed to extend the reach of SDI signals
> transmitted over cable by equalizing the input signal and generating clean
> outputs. It has one differential input and two differential output that can be
> independently controlled."

OK, applied.

>> +
>> +Required Properties :
>> +- compatible: Must be "ti,lmh0395"
>> +
>> +The device node must contain one 'port' child node per device input and
>> output
>> +port, in accordance with the video interface bindings defined in
>> +Documentation/devicetree/bindings/media/video-interfaces.txt. The port
>> nodes
>> +are numbered as follows.
>> +
>> +  Port                       LMH0395
>> +------------------------------------------------------------
>> +  SDI input          0
>> +  SDI output         1,2
>
> While there shouldn't be much doubt about SDO0 corresponding to port 1 and
> SDO1 to port 2, I'd like that to be mentioned explicitly.
>
> The device node must contain one 'port' child node per device input and output
> port, in accordance with the video interface bindings defined in
> Documentation/devicetree/bindings/media/video-interfaces.txt.
>
> The LMH0395 has three ports numbered as follows.
>
> Description            Number
> ------------------------------------------------------------
> SDI (SDI input)         0
> SDO0 (SDI output 0)     1
> SDO1 (SDI output 1)     2

OK.

>> +Example:
>> +
>> +ecspi@02010000 {
>> +     ...
>> +     ...
>> +
>> +     lmh0395@1 {
>> +             compatible = "ti,lmh0395";
>> +             reg = <1>;
>> +             spi-max-frequency = <20000000>;
>> +             ports {
>
> You need to add
>
> #address-cells = <1>;
> #size-cells = <0>;
>
> here.
>

Of course, applied.

>> +                     port@0 {
>> +                             reg = <0>;
>> +                             sdi0_in: endpoint {};
>> +                     };
>> +                     port@1 {
>> +                             reg = <1>;
>> +                             sdi0_out0: endpoint {};
>> +                     };
>> +                     port@2 {
>> +                             reg = <2>;
>> +                             /* endpoint not specified, disable output */
>> +                     };
>> +             };
>> +     };
>> +     ...
>> +};
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index cf24bb5..ca42b9e 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -9141,6 +9141,12 @@ S:     Maintained
>>  F:   sound/soc/codecs/lm49453*
>>  F:   sound/soc/codecs/isabelle*
>>
>> +TI LMH0395 DRIVER
>> +M:   Jean-Michel Hautbois <jean-michel.hautbois@xxxxxxxxxxx>
>> +L:   linux-media@xxxxxxxxxxxxxxx
>> +S:   Maintained
>> +F:   drivers/media/spi/lmh0395*
>> +
>>  TI LP855x BACKLIGHT DRIVER
>>  M:   Milo Kim <milo.kim@xxxxxx>
>>  S:   Maintained
>> diff --git a/drivers/media/spi/Kconfig b/drivers/media/spi/Kconfig
>> new file mode 100644
>> index 0000000..bcb1ab1
>> --- /dev/null
>> +++ b/drivers/media/spi/Kconfig
>> @@ -0,0 +1,14 @@
>> +if VIDEO_V4L2
>> +
>> +config VIDEO_LMH0395
>> +     tristate "LMH0395 equalizer"
>> +     depends on VIDEO_V4L2 && SPI && MEDIA_CONTROLLER && OF
>> +     ---help---
>> +       Support for TI LMH0395 3G HD/SD SDI Dual Output Low Power
>> +       Extended Reach Adaptive Cable Equalizer.
>> +
>> +       To compile this driver as a module, choose M here: the
>> +       module will be called lmh0395.
>> +
>> +
>> +endif
>> diff --git a/drivers/media/spi/Makefile b/drivers/media/spi/Makefile
>> new file mode 100644
>> index 0000000..6c587e5
>> --- /dev/null
>> +++ b/drivers/media/spi/Makefile
>> @@ -0,0 +1 @@
>> +obj-$(CONFIG_VIDEO_LMH0395)  += lmh0395.o
>> diff --git a/drivers/media/spi/lmh0395.c b/drivers/media/spi/lmh0395.c
>> new file mode 100644
>> index 0000000..3eca0df
>> --- /dev/null
>> +++ b/drivers/media/spi/lmh0395.c
>> @@ -0,0 +1,354 @@
>> +/*
>> + * LMH0395 SPI driver.
>> + * Copyright (C) 2014  Jean-Michel Hautbois
>> + *
>> + * 3G HD/SD SDI Dual Output Low Power Extended Reach Adaptive Cable
>> Equalizer
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + */
>> +
>> +#include <linux/ioctl.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/types.h>
>> +#include <linux/slab.h>
>> +#include <linux/uaccess.h>
>> +#include <linux/spi/spi.h>
>
> Please keep the headers alphabetically ordered.

OK.

>> +#include <linux/videodev2.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-of.h>
>> +
>> +static int debug;
>> +module_param(debug, int, 0644);
>> +MODULE_PARM_DESC(debug, "debug level (0-1)");
>
> I wonder whether this is really needed. If you follow my proposal to implement
> the log_status handler most v4l2_dbg call will turn into info calls. Of the
> two remaining v4l2_dbg calls one could be removed (please see below again),
> and the other turned into a dev_dbg.

I removed it, and corrected debug messages accordingly.

>> +
>> +#define      LMH0395_SPI_CMD_WRITE   0x00
>> +#define      LMH0395_SPI_CMD_READ    0x80
>> +
>> +/* Registers of LMH0395 */
>> +#define LMH0395_GENERAL_CTRL         0x00
>> +#define LMH0395_OUTPUT_DRIVER                0x01
>> +#define LMH0395_LAUNCH_AMP_CTRL              0x02
>> +#define LMH0395_MUTE_REF             0x03
>> +#define LMH0395_DEVICE_ID            0x04
>> +#define      LMH0395_RATE_INDICATOR          0x05
>> +#define      LMH0395_CABLE_LENGTH_INDICATOR  0x06
>> +#define      LMH0395_LAUNCH_AMP_INDICATION   0x07
>> +
>> +/* This is a one input, dual output device */
>> +#define LMH0395_SDI_INPUT    0
>> +#define LMH0395_SDI_OUT0     1
>> +#define LMH0395_SDI_OUT1     2
>> +
>> +#define LMH0395_PADS_NUM     3
>> +
>> +/* Register LMH0395_MUTE_REF bits [7:6] */
>> +enum lmh0395_output_type {
>> +     LMH0395_OUTPUT_TYPE_NONE,
>> +     LMH0395_OUTPUT_TYPE_SDO0,
>> +     LMH0395_OUTPUT_TYPE_SDO1,
>> +     LMH0395_OUTPUT_TYPE_BOTH
>> +};
>> +
>> +static const char * const output_strs[] = {
>> +     "No Output Driver",
>> +     "Output Driver 0",
>> +     "Output Driver 1",
>> +     "Output Driver 0+1",
>> +};
>> +
>> +
>> +/* spi implementation */
>> +
>> +static int lmh0395_spi_write(struct spi_device *spi, u8 reg, u8 data)
>> +{
>> +     int err;
>> +     u8 cmd[2];
>> +
>> +     cmd[0] = LMH0395_SPI_CMD_WRITE | reg;
>> +     cmd[1] = data;
>> +
>> +     err = spi_write(spi, cmd, 2);
>> +     if (err < 0) {
>> +             dev_err(&spi->dev, "SPI failed to select reg\n");
>
> I don't think the message is really accurate. "SPI write failed" would seem a
> better description to me. I would also print the error value, that can be
> useful for debugging.
>

OK.

>> +             return err;
>> +     }
>> +
>> +     return err;
>> +}
>> +
>> +static int lmh0395_spi_read(struct spi_device *spi, u8 reg, u8 *data)
>> +{
>> +     int err;
>> +     u8 cmd[2];
>> +     u8 read_data[2];
>> +
>> +     cmd[0] = LMH0395_SPI_CMD_READ | reg;
>> +     cmd[1] = 0xff;
>> +
>> +     err = spi_write(spi, cmd, 2);
>> +     if (err < 0) {
>> +             dev_err(&spi->dev, "SPI failed to select reg\n");
>
> Same here and below, the error value can be useful.
>
>> +             return err;
>> +     }
>> +
>> +     err = spi_read(spi, read_data, 2);
>> +     if (err < 0) {
>> +             dev_err(&spi->dev, "SPI failed to read reg\n");
>> +             return err;
>> +     }
>> +     /* The first 8 bits is the adress used, drop it */
>> +     *data = read_data[1];
>> +
>> +     return err;
>> +}
>> +
>> +struct lmh0395_state {
>> +     struct v4l2_subdev sd;
>> +     struct media_pad pads[LMH0395_PADS_NUM];
>> +     enum lmh0395_output_type output_type;
>> +};
>> +
>> +static inline struct lmh0395_state *to_state(struct v4l2_subdev *sd)
>> +{
>> +     return container_of(sd, struct lmh0395_state, sd);
>> +}
>> +
>> +static int lmh0395_set_output_type(struct v4l2_subdev *sd, u32 output)
>> +{
>> +     struct lmh0395_state *state = to_state(sd);
>> +     struct spi_device *spi = v4l2_get_subdevdata(sd);
>> +     u8 muteref_reg;
>> +
>> +     switch (output) {
>> +     case LMH0395_OUTPUT_TYPE_SDO0:
>> +             lmh0395_spi_read(spi, LMH0395_MUTE_REF, &muteref_reg);
>> +             muteref_reg &= ~(1 << 6);
>> +             break;
>> +     case LMH0395_OUTPUT_TYPE_SDO1:
>> +             lmh0395_spi_read(spi, LMH0395_MUTE_REF, &muteref_reg);
>> +             muteref_reg &= (1 << 7);
>> +             break;
>> +     case LMH0395_OUTPUT_TYPE_BOTH:
>> +             lmh0395_spi_read(spi, LMH0395_MUTE_REF, &muteref_reg);
>> +             muteref_reg |= 0x00 << 6;
>
> This line does absolutely nothing.
>
>> +             break;
>> +     case LMH0395_OUTPUT_TYPE_NONE:
>> +             lmh0395_spi_read(spi, LMH0395_MUTE_REF, &muteref_reg);
>> +             muteref_reg |= 0x11 << 6;
>
> 0x11 << 6, really ?
>
>> +             break;
>> +     default:
>> +             return -EINVAL;
>> +     }
>> +     v4l2_dbg(1, debug, sd, "Selecting %s output type\n",
>> +                                     output_strs[output]);
>> +
>> +     /* The following settings will have to be dynamic */
>> +     muteref_reg &= ~(0x1f); /* Muteref enable */
>> +     muteref_reg |= 1 << 5; /* Digital Muteref */
>
> Just set all the bits explicitly and get rid of the various reads in the case
> statements above, the function will be simpler.
>

OK, using now set_bit() and clear_bit().

>> +     lmh0395_spi_write(spi, LMH0395_MUTE_REF, muteref_reg);
>> +
>> +     state->output_type = output;
>> +     return 0;
>> +
>> +}
>> +
>> +static int lmh0395_get_rate(struct v4l2_subdev *sd, u8 *rate)
>> +{
>> +     struct spi_device *spi = v4l2_get_subdevdata(sd);
>> +     int err;
>> +     u8 ctrl;
>> +
>> +     err = lmh0395_spi_read(spi, LMH0395_RATE_INDICATOR, &ctrl);
>> +     if (err < 0)
>> +             return err;
>> +
>> +     *rate = ctrl & 0x20;
>> +     v4l2_dbg(1, debug, sd, "Rate : %s\n", (ctrl & 0x20)?"3G/HD":"SD");
>
> You need spaces around the ? and the :.
>
>> +     return 0;
>> +}
>> +
>> +static int lmh0395_get_cable_length(struct v4l2_subdev *sd, u8 rate)
>> +{
>> +     struct spi_device *spi = v4l2_get_subdevdata(sd);
>> +     u8 length;
>> +     u8 cli;
>> +     int err;
>> +
>> +     err = lmh0395_spi_read(spi, LMH0395_CABLE_LENGTH_INDICATOR, &cli);
>> +     if (err < 0)
>> +             return err;
>> +
>> +     /* The cable length indicator (CLI) provides an indication of the
>> +      * length of the cable attached to input. CLI is accessible via bits
>> +      * [7:0] of SPI register 06h.
>> +      * The 8-bit setting ranges in decimal value from 0 to 247
>> +      * ("00000000" to "11110111" binary), corresponding to 0 to 400m of
>> +      * Belden 1694A cable.
>> +      * For 3G and HD input, CLI is 1.25m per step.
>> +      * For SD input, CLI is 1.25m per step, less 20m, from 0 to 191 decimal
>> +      * and 3.5m per step from 192 to 247 decimal.
>> +      */
>> +
>> +     length = cli*5/4;
>> +     if (rate == 0) {
>> +             if (cli <= 191)
>> +                     length -= 20;
>> +             else
>> +                     length = ((191*5/4)-20) + ((cli-191)*7/2);
>> +
>> +     }
>> +     v4l2_dbg(1, debug, sd, "Length estimated (BELDEN 1694A cables) : %dm\n",
>> +                     length);
>> +     return 0;
>> +}
>> +
>> +static int lmh0395_get_control(struct v4l2_subdev *sd)
>> +{
>> +     int err;
>> +     struct spi_device *spi = v4l2_get_subdevdata(sd);
>> +     u8 ctrl;
>> +     u8 rate;
>> +
>> +     err = lmh0395_spi_read(spi, LMH0395_GENERAL_CTRL, &ctrl);
>> +     if (err < 0)
>> +             return err;
>> +
>> +     if (ctrl & 0x80) {
>> +             v4l2_dbg(1, debug, sd, "Carrier detected\n");
>> +             lmh0395_get_rate(sd, &rate);
>> +             lmh0395_get_cable_length(sd, rate);
>> +     }
>> +     return 0;
>> +}
>
> This function is only called at probe time. Is it really useful ? If you want
> to keep it you should turn it into a subdev log_status handler instead.
>

Yes, I added a log status function.

>> +static int lmh0395_s_routing(struct v4l2_subdev *sd, u32 input, u32 output,
>> +                             u32 config)
>> +{
>> +     struct lmh0395_state *state = to_state(sd);
>> +     int err = 0;
>> +
>> +     if (state->output_type != output)
>> +             err = lmh0395_set_output_type(sd, output);
>> +
>> +     return err;
>
>         if (state->output_type == output)
>                 return 0;
>
>         return lmh0395_set_output_type(sd, output);
>
> You can then get rid of the err variable.
>
> I don't really like this implementation though, the output argument is device-
> specific, you thus require explicit knowledge of the LMH0395 in your bridge
> driver.
>
> I'm not sure what the config argument is used for, but naively I would have
> used it to tell whether to enable (1) or disable (0) the route from input to
> output. The input value should then always be 0, and the output value can be 1
> or 2. Another option would be to use the new S_ROUTING userspace ioctl I've
> proposed in
> http://git.linuxtv.org/cgit.cgi/pinchartl/media.git/commit/?h=xilinx-wip&id=fc094c354338861673464ed4b8fa198089fe7bf0.

Well, right now, this is one of the two points which block me for a
new review. As this API is not there, I don't use it, maybe could I
propose the driver with the "old fashion way" ?

> Hans, could you comment on that ?
>
>> +}
>> +
>> +static const struct v4l2_subdev_video_ops lmh0395_video_ops = {
>> +     .s_routing = lmh0395_s_routing,
>> +};
>> +
>> +static const struct v4l2_subdev_ops lmh0395_ops = {
>> +     .video = &lmh0395_video_ops,
>> +};
>> +
>> +static const struct spi_device_id lmh0395_id[] = {
>> +     { "lmh0395", 0 },
>> +     { }
>> +};
>> +MODULE_DEVICE_TABLE(spi, lmh0395_id);
>> +
>> +static const struct of_device_id lmh0395_of_match[] = {
>> +     {.compatible = "ti,lmh0395", },
>> +     { /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(of, lmh0395_of_match);
>> +
>> +static int lmh0395_probe(struct spi_device *spi)
>> +{
>> +     u8 device_id;
>> +     struct lmh0395_state *state;
>> +     struct v4l2_subdev *sd;
>> +     int err;
>> +
>> +     err = lmh0395_spi_read(spi, LMH0395_DEVICE_ID, &device_id);
>> +     if (err < 0)
>> +             return err;
>> +
>> +     dev_dbg(&spi->dev, "device_id 0x%x\n", device_id);
>
> Shouldn't you validate the device ID value ?

Yes, and done now.

>> +     /* Now that the device is here, let's init V4L2 */
>> +     state = devm_kzalloc(&spi->dev, sizeof(*state), GFP_KERNEL);
>> +     if (!state)
>> +             return -ENOMEM;
>> +
>> +     sd = &state->sd;
>> +     v4l2_spi_subdev_init(sd, spi, &lmh0395_ops);
>> +
>> +     v4l2_dbg(1, debug, sd, "Configuring equalizer\n");
>
> Is this message really useful ? It will always be printed after the device_id
> message above, and thus adds little value.
>
>> +     lmh0395_get_control(sd);
>> +     /* Default is no output */
>> +     lmh0395_set_output_type(sd, LMH0395_OUTPUT_TYPE_NONE);
>> +
>> +     if (spi->dev.of_node) {
>> +             struct device_node *n = NULL;
>> +             struct of_endpoint ep;
>> +
>> +             while ((n = of_graph_get_next_endpoint(spi->dev.of_node, n))
>> +                                                             != NULL) {
>
> Philipp Zabel has submitted a patch named "of: Add for_each_endpoint_of_node
> helper macro" that should get merged in v3.19. The patch series modifies the
> behaviour of of_graph_get_next_endpoint() to drop the reference to the prev
> node. This would thus create a problem in the driver, which you should fix by
> using the for_each_endpoint_of_node() macro.
>

This is the second blocking point. Again, even if it has 7 versions
proposed right now, it is not going into 3.19 but in 3.20 at best.
So, what should I do :) ?

>> +                     of_graph_parse_endpoint(n, &ep);
>
> You should check the return value.
>

Done.

>> +                     dev_dbg(&spi->dev, "endpoint %d on port %d\n",
>> +                                             ep.id, ep.port);
>> +                     /* port 1 => SDO0 */
>> +                     if (ep.port >= 1)
>> +                             lmh0395_set_output_type(sd, ep.port);
>> +                     of_node_put(n);
>> +             }
>> +     } else {
>> +             dev_dbg(&spi->dev, "No DT configuration\n");
>
> Is that a case you want to support ?

No, but with the message, you at least know what goes wrong...

>> +     }
>> +
>> +     state->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +     state->pads[LMH0395_SDI_INPUT].flags = MEDIA_PAD_FL_SINK;
>> +     state->pads[LMH0395_SDI_OUT0].flags = MEDIA_PAD_FL_SOURCE;
>> +     state->pads[LMH0395_SDI_OUT1].flags = MEDIA_PAD_FL_SOURCE;
>> +     err = media_entity_init(&sd->entity, LMH0395_PADS_NUM, state->pads, 0);
>> +     if (err)
>> +             return err;
>> +
>> +     err = v4l2_async_register_subdev(sd);
>> +     if (err < 0) {
>> +             media_entity_cleanup(&sd->entity);
>> +             return err;
>> +     }
>> +
>> +     dev_dbg(&spi->dev, "device probed\n");
>> +
>> +     return 0;
>> +}
>> +
>> +static int lmh0395_remove(struct spi_device *spi)
>> +{
>> +     struct v4l2_subdev *sd = spi_get_drvdata(spi);
>> +
>> +     v4l2_async_unregister_subdev(sd);
>> +     media_entity_cleanup(&sd->entity);
>> +     return 0;
>> +}
>> +
>> +
>
> There's an extra blank line here.
>
>> +static struct spi_driver lmh0395_driver = {
>> +     .driver = {
>> +             .of_match_table = lmh0395_of_match,
>> +             .name = "lmh0395",
>> +             .owner = THIS_MODULE,
>> +     },
>> +     .probe = lmh0395_probe,
>> +     .remove = lmh0395_remove,
>> +     .id_table = lmh0395_id,
>> +};
>> +
>> +module_spi_driver(lmh0395_driver);
>> +
>> +MODULE_DESCRIPTION("spi device driver for LMH0395 equalizer");
>> +MODULE_AUTHOR("Jean-Michel Hautbois");
>> +MODULE_LICENSE("GPL");
>
> --
> Regards,
>
> Laurent Pinchart
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux