This driver implements tunnelling of i2c requests over GMSL via a MAX9260 deserializer. It provides an i2c adapter that can be used to reach devices on the far side of the link. Signed-off-by: Ulrich Hecht <ulrich.hecht+renesas@xxxxxxxxx> --- drivers/media/i2c/Kconfig | 6 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/max9260.c | 294 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 drivers/media/i2c/max9260.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 7c23b7a..743f8ee 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -400,6 +400,12 @@ config VIDEO_VPX3220 To compile this driver as a module, choose M here: the module will be called vpx3220. +config VIDEO_MAX9260 + tristate "Maxim MAX9260 GMSL deserializer support" + depends on I2C + ---help--- + This driver supports the Maxim MAX9260 GMSL deserializer. + comment "Video and audio decoders" config VIDEO_SAA717X diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 62323ec..9b2fd13 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -86,3 +86,4 @@ obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o obj-$(CONFIG_VIDEO_OV2659) += ov2659.o obj-$(CONFIG_VIDEO_TC358743) += tc358743.o +obj-$(CONFIG_VIDEO_MAX9260) += max9260.o diff --git a/drivers/media/i2c/max9260.c b/drivers/media/i2c/max9260.c new file mode 100644 index 0000000..2030eb0 --- /dev/null +++ b/drivers/media/i2c/max9260.c @@ -0,0 +1,294 @@ +/* + * Maxim MAX9260 GMSL Deserializer Driver + * + * Copyright (C) 2017 Ulrich Hecht + * + * 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. + */ + +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/serdev.h> +#include <linux/slab.h> +#include <linux/tty.h> + +#define SYNC 0x79 +#define ACK 0xc3 + +#define RX_FINISHED 0 +#define RX_FRAME_ERROR 1 +#define RX_EXPECT_ACK 2 +#define RX_EXPECT_ACK_DATA 3 +#define RX_EXPECT_DATA 4 + +struct max9260_device { + struct serdev_device *serdev; + u8 *rx_buf; + int rx_len; + int rx_state; + wait_queue_head_t rx_wq; + struct i2c_adapter adap; +}; + +static void wait_for_transaction(struct max9260_device *dev) +{ + wait_event_interruptible_timeout(dev->rx_wq, + dev->rx_state <= RX_FRAME_ERROR, + HZ/2); +} + +static void transact(struct max9260_device *dev, + int expect, + u8 *request, int len) +{ + serdev_device_mux_select(dev->serdev); + + serdev_device_set_baudrate(dev->serdev, 115200); + serdev_device_set_parity(dev->serdev, 1, 0); + + dev->rx_state = expect; + serdev_device_write_buf(dev->serdev, request, len); + + wait_for_transaction(dev); + + serdev_device_mux_deselect(dev->serdev); +} + +static int max9260_read_reg(struct max9260_device *dev, int reg) +{ + u8 request[] = { 0x79, 0x91, reg, 1 }; + u8 rx; + + dev->rx_len = 1; + dev->rx_buf = ℞ + + transact(dev, RX_EXPECT_ACK_DATA, request, 4); + + if (dev->rx_state == RX_FINISHED) + return rx; + + return -1; +} + +static int max9260_setup(struct max9260_device *dev) +{ + int ret; + + ret = max9260_read_reg(dev, 0x1e); + + if (ret != 0x02) { + dev_err(&dev->serdev->dev, + "device does not identify as MAX9260\n"); + return -EINVAL; + } + + return 0; +} + +static void max9260_uart_write_wakeup(struct serdev_device *serdev) +{ +} + +static int max9260_uart_receive_buf(struct serdev_device *serdev, + const u8 *data, size_t count) +{ + struct max9260_device *dev = serdev_device_get_drvdata(serdev); + int accepted; + + switch (dev->rx_state) { + case RX_FINISHED: + dev_dbg(&dev->serdev->dev, "excess data ignored\n"); + return count; + + case RX_EXPECT_ACK: + case RX_EXPECT_ACK_DATA: + if (data[0] != ACK) { + dev_dbg(&dev->serdev->dev, "frame error"); + dev->rx_state = RX_FRAME_ERROR; + wake_up_interruptible(&dev->rx_wq); + return 1; + } + switch (dev->rx_state) { + case RX_EXPECT_ACK_DATA: + dev->rx_state = RX_EXPECT_DATA; + break; + case RX_EXPECT_ACK: + dev->rx_state = RX_FINISHED; + wake_up_interruptible(&dev->rx_wq); + break; + } + return 1; + + case RX_EXPECT_DATA: + accepted = dev->rx_len < count ? dev->rx_len : count; + + memcpy(dev->rx_buf, data, accepted); + + dev->rx_len -= accepted; + dev->rx_buf += accepted; + + if (!dev->rx_len) { + dev->rx_state = RX_FINISHED; + wake_up_interruptible(&dev->rx_wq); + } + + return accepted; + + case RX_FRAME_ERROR: + dev_dbg(&dev->serdev->dev, "%d bytes ignored\n", count); + return count; + + } + return 0; +} + +struct serdev_device_ops max9260_serdev_client_ops = { + .receive_buf = max9260_uart_receive_buf, + .write_wakeup = max9260_uart_write_wakeup, +}; + +static u32 max9260_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +static s32 max9260_smbus_xfer(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, u8 command, int size, + union i2c_smbus_data *data) +{ + u8 request[] = { SYNC, + (addr << 1) + (read_write == I2C_SMBUS_READ), + command, 0, 0 }; + struct max9260_device *dev = i2c_get_adapdata(adap); + + switch (size) { + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) { + transact(dev, RX_EXPECT_ACK, request, 4); + dev_dbg(&adap->dev, + "smbus byte - addr 0x%02x, wrote 0x%02x.\n", + addr, command); + } else { + /* TBD */ + return -EOPNOTSUPP; + } + break; + + case I2C_SMBUS_BYTE_DATA: + request[3] = 1; + if (read_write == I2C_SMBUS_WRITE) { + request[4] = data->byte; + transact(dev, RX_EXPECT_ACK, request, 5); + dev_dbg(&adap->dev, + "smbus byte data - addr 0x%02x, wrote 0x%02x at 0x%02x.\n", + addr, data->byte, command); + } else { + dev->rx_len = 1; + dev->rx_buf = &data->byte; + transact(dev, RX_EXPECT_ACK_DATA, request, 4); + dev_dbg(&adap->dev, + "smbus byte data - addr 0x%02x, read 0x%02x at 0x%02x.\n", + addr, data->byte, command); + } + break; + default: + dev_dbg(&adap->dev, + "Unsupported I2C/SMBus command %d\n", size); + return -EOPNOTSUPP; + } + + if (dev->rx_state != RX_FINISHED) { + dev_dbg(&adap->dev, "xfer timed out\n"); + return -EIO; + } + + return 0; +} + +static const struct i2c_algorithm max9260_i2c_algorithm = { + .functionality = max9260_i2c_func, + .smbus_xfer = max9260_smbus_xfer, +}; + +static int max9260_probe(struct serdev_device *serdev) +{ + struct max9260_device *dev; + struct i2c_adapter *adap; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + init_waitqueue_head(&dev->rx_wq); + + dev->serdev = serdev; + serdev_device_open(serdev); + serdev_device_set_drvdata(serdev, dev); + + serdev_device_set_client_ops(serdev, &max9260_serdev_client_ops); + + ret = max9260_setup(dev); + + if (ret < 0) + goto err_free; + + adap = &dev->adap; + i2c_set_adapdata(adap, dev); + + adap->owner = THIS_MODULE; + adap->algo = &max9260_i2c_algorithm; + adap->dev.parent = &serdev->dev; + adap->retries = 5; + adap->nr = -1; + strlcpy(adap->name, dev_name(&serdev->dev), sizeof(adap->name)); + + ret = i2c_add_numbered_adapter(adap); + if (ret < 0) { + dev_err(&serdev->dev, "failed to register i2c adapter\n"); + return ret; + } + + return 0; + +err_free: + kfree(dev); + return ret; +} + +static void max9260_remove(struct serdev_device *serdev) +{ + struct max9260_device *dev = serdev_device_get_drvdata(serdev); + + serdev_device_close(dev->serdev); + + kfree(dev); +} + +static const struct of_device_id max9260_dt_ids[] = { + { .compatible = "maxim,max9260" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, max9260_dt_ids); + +static struct serdev_device_driver max9260_driver = { + .probe = max9260_probe, + .remove = max9260_remove, + .driver = { + .name = "max9260", + .of_match_table = of_match_ptr(max9260_dt_ids), + }, +}; + +module_serdev_device_driver(max9260_driver); + +MODULE_DESCRIPTION("Maxim MAX9260 GMSL Deserializer Driver"); +MODULE_AUTHOR("Ulrich Hecht"); +MODULE_LICENSE("GPL"); -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe linux-serial" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html