This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Linux/DRA726: DS90UB948-Q1 and DS90UH949-Q1

Part Number: DRA726
Other Parts Discussed in Thread: DS90UH949-Q1, DS90UB948-Q1, DS90UH928Q, DS90UH925Q

Tool/software: Linux

We have a board using DS90UB948-Q1 and DS90UH949-Q1

We have an LVDS input and an LVDS output.

Where can I get Linux drivers for these device.

In the kernel for the automotive SDK that my yocto is based on I only found support for DS90UB913aq/DS90UB914aq,DS90UH928Q  and DS90UH925Q

Michel Catudal

ACTIA Corp

  • Hello Michel,

    Unfortunately, Linux kernel does not support these serdes parts.

    You can go through the data sheet of these devices and add support for these in the  current driver.

    THere might be small changes in the register map but overall programming sequence should be almost the same.

    Update the init_seq array with values to be programmed and update the register defeinitions to match the correct part numbers.

    Regards,

    Nikhil D

  • Nikhil,

    I have created my driver based on the one in the kernel I use

    Here is the source (I removed that unused devices) Code minus the top comments

    I can't show you the schematic here but if you send me an Email I will send it to you, we have an NDA with TI.

    The 949 converts HDMI to LVDS.

    I am not sure how to setup the device tree for the 948 device.

    Looking at the schematic it looks to me like the connection might be wrong. The data output is OpenLDI and the port used is mipi (csi2_0_dx0
    to csi2_0_dx4) or I am wrong in my deduction.

    The design was reviewed by TI Engineers before we had the boards built.

    I need to know how to setup for the devicetree or if the design is wrong which chip should be used.

    ----------------------------------------------------------------------------

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/slab.h>
    #include <linux/i2c.h>
    #include <linux/gpio.h>
    #include <linux/delay.h>
    #include <linux/of_gpio.h>
    #include <linux/of_device.h>
    #include "fpd3_serdes.h"

    static const unsigned int fpd3_1080p_HDMI_ser_init[] = {
       /* digital reset1 */
       FPD3_SER_RESET,         0x02,
      /* back chan en, auto ack WR, i2c passthrough, Gate RGB data with DE*/
       FPD3_SER_GEN_CONFIG,    0x0e,
    };

    static const unsigned int fpd3_1080p_HDMI_des_init[] =
    {
       /* digital reset1 */
       FPD3_DES_RESET,     0x02,
    };

    static struct fpd3_serdes_platform_data fpd3_serdes_pdata[] = {
       {
            .name = "fpd3_HDMI_to_FPD_Link_ser"
            .dev_type = FPD3_SER_DEV,
            .ngpio = 4,
            .nslaves = 1,
            .device_id = 0x18,
            .gpio_2reg = 2 * FPD3_SER_GPIO_01 + 1,
            .init_seq = fpd3_1080p_HDMI_ser_init,
            .init_len = ARRAY_SIZE(fpd3_1080p_HDMI_ser_init),
       },
       {
            .name = "fpd3_FPD_Link_to_OpenLDI_des",
            .dev_type = FPD3_DES_DEV,
            .ngpio = 4,
            .nslaves = 1,
            .device_id = 0x2c,
            .gpio_2reg = 2 * FPD3_DES_GPIO_01 + 1,
            .init_seq = fpd3_1080p_HDMI_des_init,
            .init_len = ARRAY_SIZE(fpd3_1080p_HDMI_des_init),
       },
    };

    /* GPIO operations */
    static int __fpd3_serdes_dirval(struct gpio_chip *chip,
                unsigned offset, int value, int shift)
    {
        struct fpd3_serdes_data *data =
                container_of(chip, struct fpd3_serdes_data, chip);
        const struct fpd3_serdes_platform_data *pdata = data->pdata;
        struct i2c_client *client = data->client;
        int reg, val = 0x00;

        reg = (pdata->gpio_2reg + offset) / 2;
        if ((pdata->gpio_2reg + offset) % 2)
            shift += 4;

        val = i2c_read_le8(client, reg);

        if (value)
            val |= 1 << shift;
        else
            val &= ~(1 << shift);

        return i2c_write_le8(client, reg, val);
    }

    static int fpd3_serdes_input(struct gpio_chip *chip, unsigned offset)
    {
        return __fpd3_serdes_dirval(chip, offset,
                GPIO_DIR_INPUT, GPIO_SHIFT_DIR);
    }
    static int fpd3_serdes_output(struct gpio_chip *chip,
            unsigned offset, int value)
    {
        int ret;

        ret = __fpd3_serdes_dirval(chip, offset,
                GPIO_DIR_OUTPUT, GPIO_SHIFT_DIR);

        if (ret)
            ret = __fpd3_serdes_dirval(chip, offset,
                value, GPIO_SHIFT_VAL);

        return ret;
    }

    static void fpd3_serdes_set(struct gpio_chip *chip, unsigned offset, int value)
    {
        __fpd3_serdes_dirval(chip, offset, value, GPIO_SHIFT_VAL);
    }

    static int fpd3_serdes_get(struct gpio_chip *chip, unsigned offset)
    {
        struct fpd3_serdes_data *data =
                container_of(chip, struct fpd3_serdes_data, chip);
        const struct fpd3_serdes_platform_data *pdata = data->pdata;
        struct i2c_client *client = data->client;
        int reg;

        int shift = GPIO_SHIFT_VAL;
        reg = (pdata->gpio_2reg + offset) / 2;
        if ((pdata->gpio_2reg + offset) % 2)
            shift += 4;

        return (i2c_read_le8(client, reg) | 1 << shift) ? 1 : 0;
    }

    static struct gpio_chip fpd3_serdes_gpiochip = {
        .owner            = THIS_MODULE,
        .base            = -1,
        .ngpio            = 4,
        .can_sleep        = false,
        .get            = fpd3_serdes_get,
        .set            = fpd3_serdes_set,
        .direction_input    = fpd3_serdes_input,
        .direction_output    = fpd3_serdes_output
    };

    int fpd3_serdes_initialize(struct i2c_client *client)
    {
        struct fpd3_serdes_data *data = i2c_get_clientdata(client);
        const struct fpd3_serdes_platform_data *pdata = data->pdata;
        const unsigned int *seq;
        int i, len, reg, ret = 0, count = 0;

        if (data->slave_mode == false) {
            /* Wait for the device to initialize and show up device ID */
            reg = pdata->dev_type == FPD3_SER_DEV ?
                FPD3_SER_DEV_ID : FPD3_SER_DEV_ID;
            while (ret != pdata->device_id && count < FPD3_MAX_POLL_COUNT) {
                ret = i2c_read_le8(client, reg);
                count++;
                mdelay(3);
            }
            if (count == FPD3_MAX_POLL_COUNT) {
                dev_err(&client->dev, "Not a %s chip", pdata->name);
                return -ENODEV;
            }
        }

        len = pdata->init_len;
        seq = pdata->init_seq;
        for (i = 0; i < len; i += 2) {
            ret = i2c_write_le8(client, seq[i], seq[i+1]);
            if (ret)
                break;
            if (seq[i] == 0x01)
                udelay(500);
            else
                udelay(10);
        }

        return ret;
    }
    EXPORT_SYMBOL(fpd3_serdes_initialize);

    static int fpd3_serdes_of_probe(struct i2c_client *client,
                struct device_node *node)
    {
        struct fpd3_serdes_data *data = i2c_get_clientdata(client);
        int gpio, ret;

        gpio = of_get_gpio(node, 0);
        if (gpio_is_valid(gpio)) {
            /* Toggle the pdb bit from LOW to HIGH */
            ret = devm_gpio_request_one(&client->dev, gpio,
                    GPIOF_INIT_HIGH, "des_pdb");
        } else if (gpio == -ENOENT)
            /* Entry not present - no need to toggle pdb */
            ret = 0;
        else
            ret = gpio;

        if (ret)
            return ret;

        data->gpio_export = of_find_property(node, "gpio-controller", NULL) ?
                true : false;
        data->slave_mode = of_find_property(node, "slave-mode", NULL) ?
                true : false;
        return 0;
    }

    static const struct i2c_device_id fpd3_serdes_i2c_ids[] = {
        { "ds90ub949", 8 },
        { "ds90ub948", 8 },
        { }
    };
    MODULE_DEVICE_TABLE(i2c, fpd3_serdes_i2c_ids);

    static const struct of_device_id fpd3_serdes_dt_ids[] = {
        {.compatible = "ti,ds90ub949", &fpd3_serdes_pdata[0], },
        {.compatible = "ti,ds90ub948", &fpd3_serdes_pdata[1], },
        { }
    };

    MODULE_DEVICE_TABLE(of, fpd3_serdes_dt_ids);

    static int fpd3_serdes_probe(struct i2c_client *client,
                 const struct i2c_device_id *id)
    {
        const struct of_device_id *of_dev_id;
        const struct fpd3_serdes_platform_data *pdata;
        struct fpd3_serdes_data *data;
        int status;

        data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
        if (!data)
            return -ENOMEM;

        data->client = client;
        i2c_set_clientdata(client, data);

        of_dev_id = of_match_device(fpd3_serdes_dt_ids, &client->dev);
        if (!of_dev_id) {
            dev_err(&client->dev, "Unable to match device");
            return -EINVAL;
        }
        pdata = of_dev_id->data;
        data->pdata = pdata;

        status = fpd3_serdes_of_probe(client, client->dev.of_node);
        if (status)
            goto fail;

        if (data->slave_mode == false) {
            status = fpd3_register_i2c_adapter(client);
            if (status) {
                dev_err(&client->dev, "Cannot register i2c adapter");
                goto fail;
            }
        } else {
            status = fpd3_serdes_initialize(client);
            if (status)
                goto fail;
        }

        if (data->gpio_export == true) {
            /* Register local gpios as a separate gpiochip */
            data->chip = fpd3_serdes_gpiochip;
            data->chip.ngpio = pdata->ngpio;
            data->chip.dev = &client->dev;
            data->chip.label = client->name;
            status = gpiochip_add(&data->chip);
            if (status)
                goto fail;
        }

        if (status)
            goto unreg_gpio;

        dev_err(&client->dev, "%s %s ready", pdata->dev_type == FPD3_SER_DEV ?
            "Serializer" : "Deserializer", data->pdata->name);

        return 0;

    unreg_gpio:
        if (data->gpio_export == true) {
            gpiochip_remove(&data->chip);
            return -1;
        }
    fail:
        return status;
    }

    static int fpd3_serdes_remove(struct i2c_client *client)
    {
        struct fpd3_serdes_data    *data = i2c_get_clientdata(client);
        kfree(data);

        return 0;
    }

    static struct i2c_driver fpd3_serdes_driver = {
        .driver = {
            .name = "fpd3_serdes",
            .owner = THIS_MODULE,
            .of_match_table    = fpd3_serdes_dt_ids,
        },
        .probe = fpd3_serdes_probe,
        .remove = fpd3_serdes_remove,
        .id_table = fpd3_serdes_i2c_ids
    };
    module_i2c_driver(fpd3_serdes_driver);

    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Nikhil Devshatwar");

    ----------------------------------------------------------------------------

    Device tree entries

    &i2c2 {
            clock-frequency = <400000>;
            status = "okay";

            potentiometer@2f {
                    compatible = "microchip,mcp4018-503";
                    reg = <0x2f>;
                    status = "okay";
            };

            noa1305@39 {
                    compatible = "on,noa1305";
                    reg = <0x39>;
                    status = "okay";
            };

            lvds_des: deserializer@2c {
                    compatible = "ti,ds90ub948";
                    reg = <0x2c>;

                    #address-cells = <1>;
                    #size-cells = <0>;
            };

            lvds_ser: serializer@18 {
                    compatible = "ti,ds90ub949";
                    reg = <0x18>;

                    #address-cells = <1>;
                    #size-cells = <0>;

                    port {
                           lvds_ser: endpoint {
                           remote-endpoint = <&hdmi_out>;
                           };
                    };
            };
    };

    -----------------------------------------------------------------------------------

    Michel Catudal

    ACTIA Corp

  • Hello,

    Devicetree definition only cares about the i2c topology of the devices.
    From kernel's perspective, the fpdlink device registers following
    - A virtual i2c bus where all accesses are re-routed as i2c transactions to local bus with a alias address
    - A gpio device

    Note that the driver doesn't deal with the video conversion functionality of the serdes chips.
    The init sequnece is blindly execued by the driver to setup the video serialization/ deserialization parameters

    So it doesnt matter if the devices are converting parallel, HDMI, CSI video or anything else.
    The devicetree definition remains same.

    Please refer to this for more details on the fpdlink3 serdes driver processors.wiki.ti.com/.../Processor_SDK_VIP_Driver


    Following is an example of the i2c device topology in device tree

    DRA7xx_i2c_bus {

    /* describe all the local devices here */
    fpd3_video_serializer {
    /* this is an i2c device which also registers another i2c bus */


    /* describe all the remote devices here */
    fdp3_serializer {
    }
    camera {
    }

    hdmi_receiver {
    }

    temp_sensor {
    }
    }

    }


    Regards,
    Nikhil D