From patchwork Mon Jul 17 15:24:35 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ulrich Hecht X-Patchwork-Id: 789573 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3xB6YD4XkCz9t1t for ; Tue, 18 Jul 2017 01:25:04 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="lr4JvCVD"; dkim-atps=neutral Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751405AbdGQPZC (ORCPT ); Mon, 17 Jul 2017 11:25:02 -0400 Received: from mail-wr0-f196.google.com ([209.85.128.196]:35188 "EHLO mail-wr0-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751343AbdGQPY5 (ORCPT ); Mon, 17 Jul 2017 11:24:57 -0400 Received: by mail-wr0-f196.google.com with SMTP id a10so415562wrd.2; Mon, 17 Jul 2017 08:24:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=HL06SbFx+rqNr0ySUNam1KdthGHtD33e/85iZy+WykY=; b=lr4JvCVDC1KRxfAGy1s/fpMHSEe6swrDE3KRIEyeyARCRhHG5OWjvqxyum/aqr8H4I iu1tvPC62s0UI8ub4zNnpDzczL6eWovMwjpeF6nuQPts9ZvlbpUDNcNz173c0h0DruBj +wCOs8M3qjocoL6ushMf+aSEmYVIv0m43LWjZ1FeInHyIlcNlmqjyRKbZwNmVs8MdIUt LNj3iAmUoA7WYNfFQBCvctN8ygJODh0Ff2ZP09sF8rJ75kcR9fxPPJcp23FNruO1e4Tc W7n9S6wJx1+sw3KnA7kSatK2PwpEKNZZbJUjOpUYOWcg5w0JaSepwdjsVbZfKZLv+3DI ahKA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references; bh=HL06SbFx+rqNr0ySUNam1KdthGHtD33e/85iZy+WykY=; b=lxONXRS8srSBeCh1TsnEGhfYoV8K+xLFR6Iwf44hcKWc+m3dAM041hvDb6ruwMd5nG 2a98ONk07D/IK4cx7BNFIJ8a0yiYAVL+iiFDl3DHWt63MLHAYdXm5aBfvPEbq8KyTdm4 Qu477zpXyQ98xoibUUy0Nt6L1QN1+zlMdDxa4fRkbN7Z7nanFsbFtwkdtM6QL77+CSD/ pcsIB066NaF0giBtSdUrv0VPRwgbuM9ABwhEdKRUWTNHMEXcfpA0+pVzX4/u42osgxJn cXs6kLJAJs/NJMvRktYDYERtNKM4TaNEHQZHSvpqZd33vTaR2tCAsXil0f0QqtXwaWFr AG3g== X-Gm-Message-State: AIVw112n1anR20QH4wZ2f7A5YtJL0yvOnKlj9NUHvubIOeL+sbVj3vMf 1/qJCBBKzCJoFTL1TiM= X-Received: by 10.223.158.133 with SMTP id a5mr4184719wrf.188.1500305095693; Mon, 17 Jul 2017 08:24:55 -0700 (PDT) Received: from groucho.site (ipbcc169a0.dynamic.kabel-deutschland.de. [188.193.105.160]) by smtp.gmail.com with ESMTPSA id 13sm10802102wrl.57.2017.07.17.08.24.54 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 17 Jul 2017 08:24:55 -0700 (PDT) From: Ulrich Hecht To: linux-serial@vger.kernel.org Cc: linux-renesas-soc@vger.kernel.org, magnus.damm@gmail.com, laurent.pinchart@ideasonboard.com, wsa@the-dreams.de, robh@kernel.org, peda@axentia.se, geert@linux-m68k.org, linux-i2c@vger.kernel.org, Ulrich Hecht Subject: [RFC v2 5/6] max9260: add driver for i2c over GMSL passthrough Date: Mon, 17 Jul 2017 17:24:35 +0200 Message-Id: <1500305076-15570-6-git-send-email-ulrich.hecht+renesas@gmail.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1500305076-15570-1-git-send-email-ulrich.hecht+renesas@gmail.com> References: <1500305076-15570-1-git-send-email-ulrich.hecht+renesas@gmail.com> Sender: linux-i2c-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-i2c@vger.kernel.org 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 --- drivers/media/i2c/Kconfig | 6 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/max9260.c | 288 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 drivers/media/i2c/max9260.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 7641667..a405d67 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -423,6 +423,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 30e856c..3b6f6f2 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_VIDEO_OV7640) += ov7640.o obj-$(CONFIG_VIDEO_OV7670) += ov7670.o obj-$(CONFIG_VIDEO_OV9650) += ov9650.o obj-$(CONFIG_VIDEO_OV13858) += ov13858.o +obj-$(CONFIG_VIDEO_MAX9260) += max9260.o obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o obj-$(CONFIG_VIDEO_MT9M111) += mt9m111.o obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o diff --git a/drivers/media/i2c/max9260.c b/drivers/media/i2c/max9260.c new file mode 100644 index 0000000..ca34e67 --- /dev/null +++ b/drivers/media/i2c/max9260.c @@ -0,0 +1,288 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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 max9260_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 = ℞ + + max9260_transact(dev, RX_EXPECT_ACK_DATA, request, 4); + + if (dev->rx_state == RX_FINISHED) + return rx; + + return -EIO; +} + +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; + } + + if (dev->rx_state == RX_EXPECT_ACK_DATA) { + dev->rx_state = RX_EXPECT_DATA; + } else { + dev->rx_state = RX_FINISHED; + wake_up_interruptible(&dev->rx_wq); + } + 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_BYTE|I2C_FUNC_SMBUS_BYTE_DATA; +} + +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) { + max9260_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; + max9260_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; + max9260_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; + strlcpy(adap->name, dev_name(&serdev->dev), sizeof(adap->name)); + + ret = i2c_add_adapter(adap); + if (ret < 0) + 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");