* RE: [Patch V4 2/2] i2c: imx: add devicetree binding for lpi2c
From: Pandy Gao @ 2016-11-17 6:52 UTC (permalink / raw)
To: Vladimir Zapolskiy, wsa@the-dreams.de,
u.kleine-koenig@pengutronix.de, cmo@melexis.com, robh@kernel.org
Cc: linux-i2c@vger.kernel.org, Frank Li, Andy Duan
In-Reply-To: <19931d8c-08eb-5dd8-795f-20e900e4be4f@mleia.com>
From: Vladimir Zapolskiy <mailto:vz@mleia.com> Sent: Thursday, November 17, 2016 8:32 AM
> To: Pandy Gao <pandy.gao@nxp.com>; wsa@the-dreams.de; u.kleine-
> koenig@pengutronix.de; cmo@melexis.com; robh@kernel.org
> Cc: linux-i2c@vger.kernel.org; Frank Li <frank.li@nxp.com>; Andy Duan
> <fugang.duan@nxp.com>
> Subject: Re: [Patch V4 2/2] i2c: imx: add devicetree binding for lpi2c
>
> On 11/14/2016 11:23 AM, Gao Pan wrote:
> > Add a binding document for lpi2c driver
> >
> > Signed-off-by: Gao Pan <pandy.gao@nxp.com>
> > ---
> > .../devicetree/bindings/i2c/i2c-imx-lpi2c.txt | 20
> ++++++++++++++++++++
> > 1 file changed, 20 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/i2c/i2c-imx-lpi2c.txt
> > b/Documentation/devicetree/bindings/i2c/i2c-imx-lpi2c.txt
> > new file mode 100644
> > index 0000000..edaf04f
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/i2c/i2c-imx-lpi2c.txt
> > @@ -0,0 +1,20 @@
> > +* Freescale Low Power Inter IC (LPI2C) for i.MX
> > +
> > +Required properties:
> > +- compatible :
> > + - "fsl,imx8dv-lpi2c" for LPI2C compatible with the one integrated
> > +on i.MX8DV soc
> > + - "fsl,imx7ulp-lpi2c" for LPI2C compatible with the one integrated
> > +on i.MX7ULP soc
> > +- reg : address and length of the lpi2c master registers
> > +- interrupt-parent : core interrupt controller
> > +- interrupts : lpi2c interrupt
> > +- clocks : lpi2c clock specifier
>
> I don't know if description of standard properties is really needed, probably
> Rob can advise.
>
> My point is that pretty "interrupts-extended" is never mentioned, and all three
> interrupt* properties are well described in interrupts.txt, and in general there is
> no point to mention all those interrupts, clocks, clock-names and friends
> properties again and again.
>
> But here better to get a clarification from Rob.
Thanks.
> > +
> > +Examples:
> > +
> > +lpi2c7: lpi2c7@40A50000 {
> > + compatible = "fsl,imx8dv-lpi2c";
> > + reg = <0x40A50000 0x10000>;
> > + interrupt-parent = <&intc>;
> > + interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
> > + clocks = <&clks IMX7ULP_CLK_LPI2C7>; };
> >
>
> Please rearrange the changes, devicetree binding change should be 1/2, the
> driver change should be 2/2.
Thanks, will change it in next version.
Best Regards
Gao Pan
^ permalink raw reply
* Re: [patch v6 1/1] i2c: add master driver for mellanox systems
From: Peter Rosin @ 2016-11-17 8:05 UTC (permalink / raw)
To: vadimp, wsa; +Cc: linux-i2c, linux-kernel, jiri, Michael Shych
In-Reply-To: <1479371408-171218-1-git-send-email-vadimp@mellanox.com>
Hi!
Some comments inline.
Cheers,
Peter
On 2016-11-17 09:30, vadimp@mellanox.com wrote:
> From: Vadim Pasternak <vadimp@mellanox.com>
>
> Device driver for Mellanox I2C controller logic, implemented in Lattice
> CPLD device.
> Device supports:
> - Master mode
> - One physical bus
> - Polling mode
>
> The Kconfig currently controlling compilation of this code is:
> drivers/i2c/busses/Kconfig:config I2C_MLXCPLD
>
> Signed-off-by: Michael Shych <michaelsh@mellanox.com>
> Signed-off-by: Vadim Pasternak <vadimp@mellanox.com>
> Reviewed-by: Jiri Pirko <jiri@mellanox.com>
> Reviewed-by: Vladimir Zapolskiy <vz@mleia.com>
> ---
> v5->v6:
> Comments pointed out by Vladimir:
> - Drop the line with module path from the header;
> - In description of mlxcpld_i2c_priv remove lpc_gen_dec_reg asnd dev_id;
> - In mlxcpld_i2c_priv change type of the filed base_addr to u16 for
> the alignment with in/out and remove unused dev_id;
> - Fix misspelling in comment for mlxcpld_i2c_invalid_len;
> - Remove comment regarding EBUSY return in mlxcpld_i2c_check_busy;
> - Use sizeof of the target storage in allocation in probe routine;
> v4->v5:
> Comments pointed out by Vladimir:
> - Remove "default n" from Kconfig;
> - Fix the comments for timeout and pool time;
> - Optimize error flow in mlxcpld_i2c_probe;
> v3->v4:
> Comments pointed out by Vladimir:
> - Set default to no in Kconfig;
> - Make mlxcpld_i2c_plat_dev static and add empty line before the
> declaration;
> - In function mlxcpld_i2c_invalid_len remove (msg->len < 0), since len is
> unsigned;
> - Remove unused symbol mlxcpld_i2c_plat_dev;
> - Remove extra spaces in comments to mlxcpld_i2c_check_msg_params;
> - Remove unnecessary round braces in mlxcpld_i2c_set_transf_data;
> - Remove the assignment of 'i' variable in mlxcpld_i2c_wait_for_tc;
> - Add extra line in mlxcpld_i2c_xfer;
> - Move assignment of the adapter's fields retries and nr inside
> mlxcpld_i2c_adapter declaration;
> v2->v3:
> Comments pointed out by Vladimir:
> - Use tab symbol as indentation in Kconfig
> - Add the Kconfig section preserving the alphabetical order - added
> within "Other I2C/SMBus bus drivers" after I2C_ELEKTOR (but after this
> sections others are not follow alphabetical);
> - Change license to dual;
> - Replace ADRR with ADDR in macros;
> - Remove unused macros: MLXCPLD_LPCI2C_LPF_DFLT,
> MLXCPLD_LPCI2C_HALF_CYC_100, MLXCPLD_LPCI2C_I2C_HOLD_100,
> MLXCPLD_LPCI2C_HALF_CYC_REG, MLXCPLD_LPCI2C_I2C_HOLD_REG;
> - Fix checkpatch warnings (**/ and the end of comment);
> - Add empty line before structures mlxcpld_i2c_regs,
> mlxcpld_i2c_curr_transf, mlxcpld_i2c_priv;
> - Remove unused structure mlxcpld_i2c_regs;
> - Remove from mlxcpld_i2c_priv the next fields:
> retr_num, poll_time, block_sz, xfer_to; use instead macros
> respectively: MLXCPLD_I2C_RETR_NUM, MLXCPLD_I2C_POLL_TIME,
> MLXCPLD_I2C_DATA_REG_SZ, MLXCPLD_I2C_XFER_TO;
> - In mlxcpld_i2c_invalid_len remove unnecessary else;
> - Optimize mlxcpld_i2c_set_transf_data;
> - mlxcpld_i2c_reset - add empty lines after/before mutex
> lock/unlock;
> - mlxcpld_i2c_wait_for_free - cover case timeout is equal
> MLXCPLD_I2C_XFER_TO;
> - mlxcpld_i2c_wait_for_tc:
> - Do not assign err in declaration (also err is removed);
> - Insert empty line before case MLXCPLD_LPCI2C_ACK_IND;
> - inside case MLXCPLD_LPCI2C_ACK_IND - avoid unnecessary
> indentation;
> - Remove case MLXCPLD_LPCI2C_ERR_IND and remove this macro;
> - Add empty lines in mlxcpld_i2c_xfer before/after mutex_lock/
> mutex_unlock;
> - In mlxcpld_i2c_probe add emtpy line after platform_set_drvdata;
> - Replace platfrom handle pdev in mlxcpld_i2c_priv with the pointer
> to the structure device;
> - Place assignment of base_addr near the others;
> - Enclose e-mail with <>;
> Fixes added by Vadim:
> - Change structure description format according to
> Documentation/kernel-documentation.rst guideline;
> - mlxcpld_i2c_wait_for_tc: return error if status reaches default case;
> v1->v2
> Fixes added by Vadim:
> - Put new record in Makefile in alphabetic order;
> - Remove http://www.mellanox.com from MAINTAINERS record;
> ---
> Documentation/i2c/busses/i2c-mlxcpld | 47 +++
> MAINTAINERS | 8 +
> drivers/i2c/busses/Kconfig | 11 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-mlxcpld.c | 544 +++++++++++++++++++++++++++++++++++
> 5 files changed, 611 insertions(+)
> create mode 100644 Documentation/i2c/busses/i2c-mlxcpld
> create mode 100644 drivers/i2c/busses/i2c-mlxcpld.c
>
> diff --git a/Documentation/i2c/busses/i2c-mlxcpld b/Documentation/i2c/busses/i2c-mlxcpld
> new file mode 100644
> index 0000000..0f8678a
> --- /dev/null
> +++ b/Documentation/i2c/busses/i2c-mlxcpld
> @@ -0,0 +1,47 @@
> +Driver i2c-mlxcpld
> +
> +Author: Michael Shych <michaelsh@mellanox.com>
> +
> +This is a for Mellanox I2C controller logic, implemented in Lattice CPLD
Grammar: "This is a <what> for Mellanox..."?
> +device.
> +Device supports:
> + - Master mode.
> + - One physical bus.
> + - Polling mode.
> +
> +This controller is equipped within the next Mellanox systems:
> +"msx6710", "msx6720", "msb7700", "msn2700", "msx1410", "msn2410", "msb7800",
> +"msn2740", "msn2100".
> +
> +The next transaction types are supported:
> + - Receive Byte/Block.
> + - Send Byte/Block.
> + - Read Byte/Block.
> + - Write Byte/Block.
> +
> +Registers:
> +CTRL 0x1 - control reg.
> + Resets all the registers.
> +HALF_CYC 0x4 - cycle reg.
> + Configure the width of I2C SCL half clock cycle (in 4 LPC_CLK
> + units).
> +I2C_HOLD 0x5 - hold reg.
> + OE (output enable) is delayed by value set to this register
> + (in LPC_CLK units)
> +CMD 0x6 - command reg.
> + Bit 7(lsb), 0 = write, 1 = read.
lsb? Why is lsb in bit 7? Isn't bit 7 msb per definition? Are you
referring to the fact that this bit is the lsb on-the-wire? I.e.
are you trying to say that the fields in this register are reversed
compared to the on-wire byte format? I assume so...
> + Bits [6:0] - the 7bit Address of the I2C device.
> + It should be written last as it triggers an I2C transaction.
...but it make me wonder if the byte is bit-reversed or if it is just
fields that are out of order? Probably not, but, odd!
> +NUM_DATA 0x7 - data size reg.
> + Number of address bytes to write in read transaction
s/address/data/ ???
> +NUM_ADDR 0x8 - address reg.
> + Number of address bytes to write in read transaction.
> +STATUS 0x9 - status reg.
> + Bit 0 - transaction is completed.
> + Bit 4 - ACK/NACK.
> +DATAx 0xa - 0x54 - 68 bytes data buffer regs.
> + For write transaction address is specified in four first bytes
> + (DATA1 - DATA4), data starting from DATA4.
> + For read transactions address is send in separate transaction and
s/address is send in/the address is sent in a/
> + specified in four first bytes (DATA0 - DATA3). Data is reading
s/in four/in the four/
s/reading/read/
> + starting from DATA0.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 411e3b8..26d05f8 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7881,6 +7881,14 @@ W: http://www.mellanox.com
> Q: http://patchwork.ozlabs.org/project/netdev/list/
> F: drivers/net/ethernet/mellanox/mlxsw/
>
> +MELLANOX MLXCPLD I2C DRIVER
> +M: Vadim Pasternak <vadimp@mellanox.com>
> +M: Michael Shych <michaelsh@mellanox.com>
> +L: linux-i2c@vger.kernel.org
> +S: Supported
> +F: drivers/i2c/busses/i2c-mlxcpld.c
> +F: Documentation/i2c/busses/i2c-mlxcpld
> +
> MELLANOX MLXCPLD LED DRIVER
> M: Vadim Pasternak <vadimp@mellanox.com>
> L: linux-leds@vger.kernel.org
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index d252276..6399cea 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -1150,6 +1150,17 @@ config I2C_ELEKTOR
> This support is also available as a module. If so, the module
> will be called i2c-elektor.
>
> +config I2C_MLXCPLD
> + tristate "Mellanox I2C driver"
> + depends on X86_64
> + help
> + This exposes the Mellanox platform I2C busses to the linux I2C layer
> + for X86 based systems.
> + Controller is implemented as CPLD logic.
> +
> + This driver can also be built as a module. If so, the module will be
> + called as i2c-mlxcpld.
> +
> config I2C_PCA_ISA
> tristate "PCA9564/PCA9665 on an ISA bus"
> depends on ISA
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 29764cc..645bf08 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -116,6 +116,7 @@ obj-$(CONFIG_I2C_BCM_KONA) += i2c-bcm-kona.o
> obj-$(CONFIG_I2C_BRCMSTB) += i2c-brcmstb.o
> obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += i2c-cros-ec-tunnel.o
> obj-$(CONFIG_I2C_ELEKTOR) += i2c-elektor.o
> +obj-$(CONFIG_I2C_MLXCPLD) += i2c-mlxcpld.o
> obj-$(CONFIG_I2C_OPAL) += i2c-opal.o
> obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o
> obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o
> diff --git a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c
> new file mode 100644
> index 0000000..07d5657
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-mlxcpld.c
> @@ -0,0 +1,544 @@
> +/*
> + * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
> + * Copyright (c) 2016 Michael Shych <michaels@mellanox.com>
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions are met:
> + *
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + * 3. Neither the names of the copyright holders nor the names of its
> + * contributors may be used to endorse or promote products derived from
> + * this software without specific prior written permission.
> + *
> + * Alternatively, this software may be distributed under the terms of the
> + * GNU General Public License ("GPL") version 2 as published by the Free
> + * Software Foundation.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> + * POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +/* General defines */
> +#define MLXPLAT_CPLD_LPC_I2C_BASE_ADDR 0x2000
> +#define MLXCPLD_I2C_DEVICE_NAME "i2c_mlxcpld"
> +#define MLXCPLD_I2C_VALID_FLAG (I2C_M_RECV_LEN | I2C_M_RD)
> +#define MLXCPLD_I2C_BUS_NUM 1
> +#define MLXCPLD_I2C_DATA_REG_SZ 36
> +#define MLXCPLD_I2C_MAX_ADDR_LEN 4
> +#define MLXCPLD_I2C_RETR_NUM 2
> +#define MLXCPLD_I2C_XFER_TO 500000 /* usec */
> +#define MLXCPLD_I2C_POLL_TIME 2000 /* usec */
> +
> +/* LPC I2C registers */
> +#define MLXCPLD_LPCI2C_LPF_REG 0x0
> +#define MLXCPLD_LPCI2C_CTRL_REG 0x1
> +#define MLXCPLD_LPCI2C_HALF_CYC_REG 0x4
> +#define MLXCPLD_LPCI2C_I2C_HOLD_REG 0x5
> +#define MLXCPLD_LPCI2C_CMD_REG 0x6
> +#define MLXCPLD_LPCI2C_NUM_DAT_REG 0x7
> +#define MLXCPLD_LPCI2C_NUM_ADDR_REG 0x8
> +#define MLXCPLD_LPCI2C_STATUS_REG 0x9
> +#define MLXCPLD_LPCI2C_DATA_REG 0xa
> +
> +/* LPC I2C masks and parametres */
> +#define MLXCPLD_LPCI2C_RST_SEL_MASK 0x1
> +#define MLXCPLD_LPCI2C_TRANS_END 0x1
> +#define MLXCPLD_LPCI2C_STATUS_NACK 0x10
> +#define MLXCPLD_LPCI2C_NO_IND 0
> +#define MLXCPLD_LPCI2C_ACK_IND 1
> +#define MLXCPLD_LPCI2C_NACK_IND 2
> +
> +/**
> + * struct mlxcpld_i2c_curr_transf - current transaction parameters:
"xfer" seems to be a more common abbreviation of "transfer".
> + * @cmd: command;
> + * @addr_width: address width;
> + * @data_len: data length;
> + * @cmd: command register;
> + * @msg_num: message number;
> + * @msg: pointer to message buffer;
> + */
> +
> +struct mlxcpld_i2c_curr_transf {
Dito.
> + u8 cmd;
> + u8 addr_width;
> + u8 data_len;
> + u8 msg_num;
> + struct i2c_msg *msg;
> +};
> +
> +/**
> + * struct mlxcpld_i2c_priv - private controller data:
> + * @adap: i2c adapter;
> + * @base_addr: base IO address;
> + * @lock: bus access lock;
> + * @xfer: current i2c transfer block;
> + * @dev: device handle;
> + */
> +
> +struct mlxcpld_i2c_priv {
> + struct i2c_adapter adap;
> + u32 base_addr;
> + struct mutex lock;
> + struct mlxcpld_i2c_curr_transf xfer;
> + struct device *dev;
> +};
> +
> +static void mlxcpld_i2c_lpc_write_buf(u8 *data, u8 len, u32 addr)
> +{
> + int i, nbyte, ndword;
> +
> + nbyte = len % 4;
> + ndword = len / 4;
> + for (i = 0; i < ndword; i++)
> + outl(*((u32 *)data + i), addr + i * 4);
> + ndword *= 4;
> + addr += ndword;
> + data += ndword;
> + for (i = 0; i < nbyte; i++)
> + outb(*(data + i), addr + i);
> +}
I find the below easier to read (untested):
static void mlxcpld_i2c_lpc_write_buf(u8 *data, u8 len, u32 addr)
{
int i;
for (i = 0; i < len - len % 4; i += 4)
outl(*(u32 *)(data + i), addr + i);
for (; i < len; ++i)
outb(*(data + i), addr + i);
}
> +
> +static void mlxcpld_i2c_lpc_read_buf(u8 *data, u8 len, u32 addr)
> +{
> + int i, nbyte, ndword;
> +
> + nbyte = len % 4;
> + ndword = len / 4;
> + for (i = 0; i < ndword; i++)
> + *((u32 *)data + i) = inl(addr + i * 4);
> + ndword *= 4;
> + addr += ndword;
> + data += ndword;
> + for (i = 0; i < nbyte; i++)
> + *(data + i) = inb(addr + i);
> +}
Similar to the above, of course.
> +
> +static void mlxcpld_i2c_read_comm(struct mlxcpld_i2c_priv *priv, u8 offs,
> + u8 *data, u8 datalen)
> +{
> + u32 addr = priv->base_addr + offs;
> +
> + switch (datalen) {
> + case 1:
> + *(data) = inb(addr);
> + break;
> + case 2:
> + *((u16 *)data) = inw(addr);
> + break;
> + case 3:
> + *((u16 *)data) = inw(addr);
> + *(data + 2) = inb(addr + 2);
> + break;
> + case 4:
> + *((u32 *)data) = inl(addr);
> + break;
> + default:
> + mlxcpld_i2c_lpc_read_buf(data, datalen, addr);
> + break;
> + }
> +}
> +
> +static void mlxcpld_i2c_write_comm(struct mlxcpld_i2c_priv *priv, u8 offs,
> + u8 *data, u8 datalen)
> +{
> + u32 addr = priv->base_addr + offs;
> +
> + switch (datalen) {
> + case 1:
> + outb(*(data), addr);
> + break;
> + case 2:
> + outw(*((u16 *)data), addr);
> + break;
> + case 3:
> + outw(*((u16 *)data), addr);
> + outb(*(data + 2), addr + 2);
> + break;
> + case 4:
> + outl(*((u32 *)data), addr);
> + break;
> + default:
> + mlxcpld_i2c_lpc_write_buf(data, datalen, addr);
> + break;
> + }
> +}
> +
> +/* Check validity of current i2c message and all transfer.
> + * Calculate also common length of all i2c messages in transfer.
> + */
Multiline comments should start with a /* on a line on its own,
to form symmetry with the trailing */.
> +static int mlxcpld_i2c_invalid_len(struct mlxcpld_i2c_priv *priv,
> + const struct i2c_msg *msg, u8 *comm_len)
> +{
> + u8 max_len = msg->flags == I2C_M_RD ? MLXCPLD_I2C_DATA_REG_SZ -
> + MLXCPLD_I2C_MAX_ADDR_LEN : MLXCPLD_I2C_DATA_REG_SZ;
> +
> + if (msg->len > max_len)
> + return -EINVAL;
> +
> + *comm_len += msg->len;
> + if (*comm_len > MLXCPLD_I2C_DATA_REG_SZ)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +/* Check validity of received i2c messages parameters.
> + * Returns 0 if OK, other - in case of invalid parameters
> + * or common length of data that should be passed to CPLD
> + */
Dito.
> +static int mlxcpld_i2c_check_msg_params(struct mlxcpld_i2c_priv *priv,
> + struct i2c_msg *msgs, int num,
> + u8 *comm_len)
> +{
> + int i;
> +
> + if (!num) {
> + dev_err(priv->dev, "Incorrect 0 num of messages\n");
> + return -EINVAL;
> + }
> +
> + if (unlikely(msgs[0].addr > 0x7f)) {
> + dev_err(priv->dev, "Invalid address 0x%03x\n",
> + msgs[0].addr);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < num; ++i) {
> + if (unlikely(!msgs[i].buf)) {
> + dev_err(priv->dev, "Invalid buf in msg[%d]\n",
> + i);
> + return -EINVAL;
> + }
> + if (unlikely(msgs[0].addr != msgs[i].addr)) {
> + dev_err(priv->dev, "Invalid addr in msg[%d]\n",
> + i);
> + return -EINVAL;
> + }
> + if (unlikely(mlxcpld_i2c_invalid_len(priv, &msgs[i],
> + comm_len))) {
> + dev_err(priv->dev, "Invalid len %d msg[%d], addr 0x%x, lag %u\n",
> + msgs[i].len, i, msgs[i].addr, msgs[i].flags);
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/* Check if transfer is completed and status of operation.
> + * Returns 0 - transfer completed (both ACK or NACK),
> + * negative - transfer isn't finished.
> + */
Dito.
> +static int mlxcpld_i2c_check_status(struct mlxcpld_i2c_priv *priv, int *status)
> +{
> + u8 val;
> +
> + mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_STATUS_REG, &val, 1);
> +
> + if (val & MLXCPLD_LPCI2C_TRANS_END) {
> + if (val & MLXCPLD_LPCI2C_STATUS_NACK)
> + /* The slave is unable to accept the data. No such
> + * slave, command not understood, or unable to accept
> + * any more data.
> + */
Dito, and I see more below...
> + *status = MLXCPLD_LPCI2C_NACK_IND;
> + else
> + *status = MLXCPLD_LPCI2C_ACK_IND;
> + return 0;
> + }
> + *status = MLXCPLD_LPCI2C_NO_IND;
> +
> + return -EIO;
> +}
> +
> +static void mlxcpld_i2c_set_transf_data(struct mlxcpld_i2c_priv *priv,
> + struct i2c_msg *msgs, int num,
> + u8 comm_len)
> +{
> + priv->xfer.msg = msgs;
> + priv->xfer.msg_num = num;
> +
> + /*
> + * All upper layers currently are never use transfer with more than
> + * 2 messages. Actually, it's also not so relevant in Mellanox systems
> + * because of HW limitation. Max size of transfer is o more than 20B
> + * in current x86 LPCI2C bridge.
> + */
> + priv->xfer.cmd = msgs[num - 1].flags & I2C_M_RD;
> +
> + if (priv->xfer.cmd == I2C_M_RD && comm_len != msgs[0].len) {
> + priv->xfer.addr_width = msgs[0].len;
> + priv->xfer.data_len = comm_len - priv->xfer.addr_width;
> + } else {
> + priv->xfer.addr_width = 0;
> + priv->xfer.data_len = comm_len;
> + }
> +}
> +
> +/* Reset CPLD LPCI2C block */
> +static void mlxcpld_i2c_reset(struct mlxcpld_i2c_priv *priv)
> +{
> + u8 val;
> +
> + mutex_lock(&priv->lock);
> +
> + mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_CTRL_REG, &val, 1);
> + val &= ~MLXCPLD_LPCI2C_RST_SEL_MASK;
> + mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_CTRL_REG, &val, 1);
> +
> + mutex_unlock(&priv->lock);
> +}
> +
> +/* Make sure the CPLD is ready to start transmitting. */
> +static int mlxcpld_i2c_check_busy(struct mlxcpld_i2c_priv *priv)
> +{
> + u8 val;
> +
> + mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_STATUS_REG, &val, 1);
> +
> + if (val & MLXCPLD_LPCI2C_TRANS_END)
> + return 0;
> +
> + return -EIO;
> +}
> +
> +static int mlxcpld_i2c_wait_for_free(struct mlxcpld_i2c_priv *priv)
> +{
> + int timeout = 0;
> +
> + do {
> + if (!mlxcpld_i2c_check_busy(priv))
> + break;
> + usleep_range(MLXCPLD_I2C_POLL_TIME / 2, MLXCPLD_I2C_POLL_TIME);
> + timeout += MLXCPLD_I2C_POLL_TIME;
> + } while (timeout <= MLXCPLD_I2C_XFER_TO);
> +
> + if (timeout > MLXCPLD_I2C_XFER_TO)
> + return -ETIMEDOUT;
> +
> + return 0;
> +}
> +
> +/*
> + * Wait for master transfer to complete.
> + * It puts current process to sleep until we get interrupt or timeout expires.
> + * Returns the number of transferred or read bytes or error (<0).
> + */
> +static int mlxcpld_i2c_wait_for_tc(struct mlxcpld_i2c_priv *priv)
> +{
> + int status, i, timeout = 0;
> + u8 datalen;
> +
> + do {
> + usleep_range(MLXCPLD_I2C_POLL_TIME / 2, MLXCPLD_I2C_POLL_TIME);
> + if (!mlxcpld_i2c_check_status(priv, &status))
> + break;
> + timeout += MLXCPLD_I2C_POLL_TIME;
> + } while (status == 0 && timeout < MLXCPLD_I2C_XFER_TO);
> +
> + switch (status) {
> + case MLXCPLD_LPCI2C_NO_IND:
> + return -ETIMEDOUT;
> +
> + case MLXCPLD_LPCI2C_ACK_IND:
> + if (priv->xfer.cmd != I2C_M_RD)
> + return (priv->xfer.addr_width + priv->xfer.data_len);
> +
> + if (priv->xfer.msg_num == 1)
> + i = 0;
> + else
> + i = 1;
> +
> + if (!priv->xfer.msg[i].buf)
> + return -EINVAL;
> +
> + /*
> + * Actual read data len will be always the same as
> + * requested len. 0xff (line pull-up) will be returned
> + * if slave has no data to return. Thus don't read
> + * MLXCPLD_LPCI2C_NUM_DAT_REG reg from CPLD.
> + */
> + datalen = priv->xfer.data_len;
> +
> + mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_DATA_REG,
> + priv->xfer.msg[i].buf, datalen);
> +
> + return datalen;
> +
> + case MLXCPLD_LPCI2C_NACK_IND:
> + return -EAGAIN;
> +
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static void mlxcpld_i2c_xfer_msg(struct mlxcpld_i2c_priv *priv)
> +{
> + int i, len = 0;
> + u8 cmd;
> +
> + mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_NUM_DAT_REG,
> + &priv->xfer.data_len, 1);
> + mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_NUM_ADDR_REG,
> + &priv->xfer.addr_width, 1);
> +
> + for (i = 0; i < priv->xfer.msg_num; i++) {
> + if ((priv->xfer.msg[i].flags & I2C_M_RD) != I2C_M_RD) {
> + /* Don't write to CPLD buffer in read transaction */
> + mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_DATA_REG +
> + len, priv->xfer.msg[i].buf,
> + priv->xfer.msg[i].len);
> + len += priv->xfer.msg[i].len;
> + }
> + }
> +
> + /* Set target slave address with command for master transfer.
> + * It should be latest executed function before CPLD transaction.
> + */
> + cmd = (priv->xfer.msg[0].addr << 1) | priv->xfer.cmd;
> + mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_CMD_REG, &cmd, 1);
> +}
> +
> +/* Generic lpc-i2c transfer.
> + * Returns the number of processed messages or error (<0).
> + */
> +static int mlxcpld_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
> + int num)
> +{
> + struct mlxcpld_i2c_priv *priv = i2c_get_adapdata(adap);
> + u8 comm_len = 0;
> + int err;
> +
> + err = mlxcpld_i2c_check_msg_params(priv, msgs, num, &comm_len);
> + if (err) {
> + dev_err(priv->dev, "Incorrect message\n");
> + return err;
> + }
> +
> + /* Check bus state */
> + if (mlxcpld_i2c_wait_for_free(priv)) {
> + dev_err(priv->dev, "LPCI2C bridge is busy\n");
> +
> + /*
> + * Usually it means something serious has happened.
> + * We can not have unfinished previous transfer
> + * so it doesn't make any sense to try to stop it.
> + * Probably we were not able to recover from the
> + * previous error.
> + * The only reasonable thing - is soft reset.
> + */
> + mlxcpld_i2c_reset(priv);
> + if (mlxcpld_i2c_check_busy(priv)) {
> + dev_err(priv->dev, "LPCI2C bridge is busy after reset\n");
> + return -EIO;
> + }
> + }
> +
> + mlxcpld_i2c_set_transf_data(priv, msgs, num, comm_len);
> +
> + mutex_lock(&priv->lock);
> +
> + /* Do real transfer. Can't fail */
> + mlxcpld_i2c_xfer_msg(priv);
> +
> + /* Wait for transaction complete */
> + err = mlxcpld_i2c_wait_for_tc(priv);
> +
> + mutex_unlock(&priv->lock);
> +
> + return err < 0 ? err : num;
> +}
> +
> +static u32 mlxcpld_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
> +}
> +
> +static const struct i2c_algorithm mlxcpld_i2c_algo = {
> + .master_xfer = mlxcpld_i2c_xfer,
> + .functionality = mlxcpld_i2c_func
> +};
> +
> +static struct i2c_adapter mlxcpld_i2c_adapter = {
> + .owner = THIS_MODULE,
> + .name = "i2c-mlxcpld",
> + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
> + .algo = &mlxcpld_i2c_algo,
> + .retries = MLXCPLD_I2C_RETR_NUM,
> + .nr = MLXCPLD_I2C_BUS_NUM,
> +};
> +
> +static int mlxcpld_i2c_probe(struct platform_device *pdev)
> +{
> + struct mlxcpld_i2c_priv *priv;
> + int err;
> +
> + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + mutex_init(&priv->lock);
> + platform_set_drvdata(pdev, priv);
> +
> + priv->dev = &pdev->dev;
> +
> + /* Register with i2c layer */
> + mlxcpld_i2c_adapter.timeout = usecs_to_jiffies(MLXCPLD_I2C_XFER_TO);
> + priv->adap = mlxcpld_i2c_adapter;
> + priv->adap.dev.parent = &pdev->dev;
> + priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADDR;
> + i2c_set_adapdata(&priv->adap, priv);
> +
> + err = i2c_add_numbered_adapter(&priv->adap);
> + if (err)
> + mutex_destroy(&priv->lock);
> +
> + return err;
> +}
> +
> +static int mlxcpld_i2c_remove(struct platform_device *pdev)
> +{
> + struct mlxcpld_i2c_priv *priv = platform_get_drvdata(pdev);
> +
> + i2c_del_adapter(&priv->adap);
> + mutex_destroy(&priv->lock);
> +
> + return 0;
> +}
> +
> +static struct platform_driver mlxcpld_i2c_driver = {
> + .probe = mlxcpld_i2c_probe,
> + .remove = mlxcpld_i2c_remove,
> + .driver = {
> + .name = MLXCPLD_I2C_DEVICE_NAME,
> + },
> +};
> +
> +module_platform_driver(mlxcpld_i2c_driver);
> +
> +MODULE_AUTHOR("Michael Shych <michaels@mellanox.com>");
> +MODULE_DESCRIPTION("Mellanox I2C-CPLD controller driver");
> +MODULE_LICENSE("Dual BSD/GPL");
> +MODULE_ALIAS("platform:i2c-mlxcpld");
>
^ permalink raw reply
* RE: [Patch V4 1/2] i2c: imx: add low power i2c bus driver
From: Pandy Gao @ 2016-11-17 6:52 UTC (permalink / raw)
To: Vladimir Zapolskiy, wsa@the-dreams.de,
u.kleine-koenig@pengutronix.de, cmo@melexis.com, robh@kernel.org
Cc: linux-i2c@vger.kernel.org, Frank Li, Andy Duan
In-Reply-To: <62c45c5d-0567-99b6-b435-1f01408f61db@mleia.com>
From: Vladimir Zapolskiy <mailto:vz@mleia.com> Sent: Thursday, November 17, 2016 8:59 AM
> To: Pandy Gao <pandy.gao@nxp.com>; wsa@the-dreams.de; u.kleine-
> koenig@pengutronix.de; cmo@melexis.com; robh@kernel.org
> Cc: linux-i2c@vger.kernel.org; Frank Li <frank.li@nxp.com>; Andy Duan
> <fugang.duan@nxp.com>
> Subject: Re: [Patch V4 1/2] i2c: imx: add low power i2c bus driver
>
> On 11/14/2016 11:23 AM, Gao Pan wrote:
> > This patch adds lpi2c bus driver to support new i.MX products which
> > use lpi2c instead of the old imx i2c.
> >
> > The lpi2c can continue operating in stop mode when an appropriate
> > clock is available. It is also designed for low CPU overhead with DMA
> > offloading of FIFO register accesses.
> >
> > Signed-off-by: Gao Pan <pandy.gao@nxp.com>
> > Reviewed-by: Fugang Duan <B38611@freescale.com>
> > ---
>
> [snip]
>
> > +
> > +static int lpi2c_imx_master_enable(struct lpi2c_imx_struct
> > +*lpi2c_imx) {
> > + unsigned int temp;
> > + int ret;
> > +
> > + ret = clk_enable(lpi2c_imx->clk);
> > + if (ret)
> > + return ret;
> > +
> > + temp = MCR_RST;
> > + writel(temp, lpi2c_imx->base + LPI2C_MCR);
> > + writel(0, lpi2c_imx->base + LPI2C_MCR);
> > +
> > + ret = lpi2c_imx_config(lpi2c_imx);
> > + if (ret)
> > + return ret;
>
> Missing clk_disable() on error path.
Thanks, will change it in next version.
> > +
> > + temp = readl(lpi2c_imx->base + LPI2C_MCR);
> > + temp |= MCR_MEN;
> > + writel(temp, lpi2c_imx->base + LPI2C_MCR);
> > +
> > + return 0;
> > +}
> > +
> > +static int lpi2c_imx_master_disable(struct lpi2c_imx_struct
> > +*lpi2c_imx) {
> > + unsigned int temp = 0;
>
> No need of assignment, just do a declaration "u32 temp;".
Thanks.
> > +
> > + temp = readl(lpi2c_imx->base + LPI2C_MCR);
> > + temp &= ~MCR_MEN;
> > + writel(temp, lpi2c_imx->base + LPI2C_MCR);
> > +
> > + clk_disable(lpi2c_imx->clk);
> > +
> > + return 0;
> > +}
> > +
> > +static int lpi2c_imx_msg_complete(struct lpi2c_imx_struct *lpi2c_imx)
> > +{
> > + unsigned int timeout;
>
> unsigned long timeout;
Thanks.
> > +
> > + timeout = wait_for_completion_timeout(&lpi2c_imx->complete, HZ);
> > +
> > + return timeout ? 0 : -ETIMEDOUT;
> > +}
> > +
>
> [snip]
>
> > +static void lpi2c_imx_read_rxfifo(struct lpi2c_imx_struct *lpi2c_imx)
> > +{
> > + unsigned int blocklen, remaining;
> > + unsigned int temp, data;
> > +
> > + do {
> > + data = readl(lpi2c_imx->base + LPI2C_MRDR);
> > + if (data & MRDR_RXEMPTY)
> > + break;
> > +
> > + lpi2c_imx->rx_buf[lpi2c_imx->delivered++] = data & 0xff;
> > + } while (1);
> > +
> > + /*
> > + * First byte is the length of remaining packet in the SMBus block
> > + * data read. Add it to msgs->len.
> > + */
> > + if (lpi2c_imx->block_data) {
> > + blocklen = lpi2c_imx->rx_buf[0];
> > + lpi2c_imx->msglen += blocklen;
> > + }
> > +
> > + remaining = lpi2c_imx->msglen - lpi2c_imx->delivered;
> > +
> > + if (!remaining) {
> > + complete(&lpi2c_imx->complete);
> > + return;
> > + }
> > +
> > + /* not finished, still waiting for rx data */
> > + lpi2c_imx_set_rx_watermark(lpi2c_imx);
> > +
> > + /* multiple receive commands */
> > + if (lpi2c_imx->block_data) {
> > + lpi2c_imx->block_data = 0;
> > + temp = remaining;
> > + temp |= (RECV_DATA << 8);
> > + writel(temp, lpi2c_imx->base + LPI2C_MTDR);
> > + } else if (!(lpi2c_imx->delivered & 0xff)) {
> > + temp = remaining > CHUNK_DATA ? CHUNK_DATA - 1 :
> (remaining - 1);
>
> checkpatch complains:
>
> WARNING: line over 80 characters
> #473: FILE: drivers/i2c/busses/i2c-imx-lpi2c.c:421:
> + temp = remaining > CHUNK_DATA ? CHUNK_DATA - 1 : (remaining - 1);
>
> May be rewrite it as
>
> temp = (remaining > CHUNK_DATA ? CHUNK_DATA : remaining) - 1;
Thanks, will change it in next version.
> > + temp |= (RECV_DATA << 8);
> > + writel(temp, lpi2c_imx->base + LPI2C_MTDR);
> > + }
> > +
> > + lpi2c_imx_intctrl(lpi2c_imx, MIER_RDIE); }
> > +
>
> [snip]
>
> > +
> > +static u32 lpi2c_imx_func(struct i2c_adapter *adapter) {
> > + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
> > + I2C_FUNC_SMBUS_READ_BLOCK_DATA;
>
> Please indent deeper the second line.
Thanks.
> > +}
> > +
> > +static struct i2c_algorithm lpi2c_imx_algo = {
> > + .master_xfer = lpi2c_imx_xfer,
> > + .functionality = lpi2c_imx_func,
> > +};
> > +
> > +static const struct of_device_id lpi2c_imx_of_match[] = {
> > + { .compatible = "fsl,imx8dv-lpi2c" },
> > + { .compatible = "fsl,imx7ulp-lpi2c" },
>
> Please sort the compatibles alphanumerically.
Thanks, will change it in next version.
> > + { },
> > +};
> > +MODULE_DEVICE_TABLE(of, lpi2c_imx_of_match)
>
> Missing closing ";" ^^^^^^
Thanks.
> > +
> > +static int lpi2c_imx_probe(struct platform_device *pdev) {
> > + struct lpi2c_imx_struct *lpi2c_imx;
> > + struct resource *res;
> > + int irq, ret;
> > +
> > + lpi2c_imx = devm_kzalloc(&pdev->dev,
> > + sizeof(*lpi2c_imx), GFP_KERNEL);
>
> Please place it on a single line, it fits into 80 character limit.
Thanks, will change it in next version.
> > + if (!lpi2c_imx)
> > + return -ENOMEM;
> > +
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + lpi2c_imx->base = devm_ioremap_resource(&pdev->dev, res);
> > + if (IS_ERR(lpi2c_imx->base))
> > + return PTR_ERR(lpi2c_imx->base);
> > +
> > + irq = platform_get_irq(pdev, 0);
> > + if (irq < 0) {
> > + dev_err(&pdev->dev, "can't get irq number\n");
> > + return irq;
> > + }
> > +
> > + lpi2c_imx->adapter.owner = THIS_MODULE;
> > + lpi2c_imx->adapter.algo = &lpi2c_imx_algo;
> > + lpi2c_imx->adapter.dev.parent = &pdev->dev;
> > + lpi2c_imx->adapter.dev.of_node = pdev->dev.of_node;
> > + strlcpy(lpi2c_imx->adapter.name, pdev->name,
> > + sizeof(lpi2c_imx->adapter.name));
>
> checkpatch --strict complains:
>
> CHECK: Alignment should match open parenthesis
> #630: FILE: drivers/i2c/busses/i2c-imx-lpi2c.c:578:
> + strlcpy(lpi2c_imx->adapter.name, pdev->name,
> + sizeof(lpi2c_imx->adapter.name));
Thanks, will change it in next version.
> > +
> > + lpi2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
> > + if (IS_ERR(lpi2c_imx->clk)) {
> > + dev_err(&pdev->dev, "can't get I2C peripheral clock\n");
> > + return PTR_ERR(lpi2c_imx->clk);
> > + }
> > +
> > + ret = clk_prepare(lpi2c_imx->clk);
> > + if (ret) {
> > + dev_err(&pdev->dev, "clk prepare failed %d\n", ret);
> > + return ret;
> > + }
> > +
> > + ret = of_property_read_u32(pdev->dev.of_node,
> > + "clock-frequency", &lpi2c_imx->bitrate);
> > + if (ret)
> > + lpi2c_imx->bitrate = LPI2C_DEFAULT_RATE;
> > +
> > + ret = devm_request_irq(&pdev->dev, irq, lpi2c_imx_isr, 0,
> > + pdev->name, lpi2c_imx);
> > + if (ret) {
> > + dev_err(&pdev->dev, "can't claim irq %d\n", irq);
> > + return ret;
>
> On error path you leak clk_prepare()'d lpi2c_imx->clk clock.
>
> I would recommend to prepare the clock after successful adapter registration.
Thanks! But it's better that clk is prepared before adapter registration. Because during adapter registration, i2c slave will probe.
As a result, i2c xfer will be called. In such situation, clk_enable() is called while clk has not been prepared.
> > + }
> > +
> > + i2c_set_adapdata(&lpi2c_imx->adapter, lpi2c_imx);
> > + platform_set_drvdata(pdev, lpi2c_imx);
> > +
> > + ret = i2c_add_adapter(&lpi2c_imx->adapter);
> > + if (ret) {
> > + dev_err(&pdev->dev, "registration failed\n");
>
> Please remove this notification line, it is reported by I2C core.
Thanks, will remove it in next version.
> > + return ret;
> > + }
> > +
> > + dev_info(&lpi2c_imx->adapter.dev, "LPI2C adapter registered\n");
> > +
> > + return 0;
> > +}
> > +
> > +static int lpi2c_imx_remove(struct platform_device *pdev) {
> > + struct lpi2c_imx_struct *lpi2c_imx = platform_get_drvdata(pdev);
> > +
>
> Missing clk_unprepare(lpi2c_imx->clk clock);
Yes, you are right. Thanks.
> > + i2c_del_adapter(&lpi2c_imx->adapter);
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver lpi2c_imx_driver = {
> > + .probe = lpi2c_imx_probe,
> > + .remove = lpi2c_imx_remove,
> > + .driver = {
> > + .name = DRIVER_NAME,
> > + .of_match_table = lpi2c_imx_of_match,
> > + },
> > +};
> > +
> > +module_platform_driver(lpi2c_imx_driver);
> > +
> > +MODULE_AUTHOR("Gao Pan <pandy.gao@nxp.com>");
> MODULE_DESCRIPTION("I2C
> > +adapter driver for LPI2C bus"); MODULE_LICENSE("GPL v2");
Thanks again for your valuable comments!
Best Regard
Gao Pan
^ permalink raw reply
* [RFC PATCH 5/7] iio: multiplexer: new iio category and iio-mux driver
From: Peter Rosin @ 2016-11-16 23:48 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Arnd Bergmann, Greg Kroah-Hartman,
linux-i2c, devicetree, linux-iio
In-Reply-To: <1479340111-1259-1-git-send-email-peda@axentia.se>
When a multiplexer changes how an iio device behaves (for example
by feeding different signals to an ADC), this driver can be used
create one virtual iio channel for each multiplexer state.
Depends on the generic multiplexer driver.
---
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/multiplexer/Kconfig | 17 +++
drivers/iio/multiplexer/Makefile | 6 +
drivers/iio/multiplexer/iio-mux.c | 313 ++++++++++++++++++++++++++++++++++++++
5 files changed, 338 insertions(+)
create mode 100644 drivers/iio/multiplexer/Kconfig
create mode 100644 drivers/iio/multiplexer/Makefile
create mode 100644 drivers/iio/multiplexer/iio-mux.c
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 6743b18194fb..dcb541d0d70e 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -82,6 +82,7 @@ source "drivers/iio/humidity/Kconfig"
source "drivers/iio/imu/Kconfig"
source "drivers/iio/light/Kconfig"
source "drivers/iio/magnetometer/Kconfig"
+source "drivers/iio/multiplexer/Kconfig"
source "drivers/iio/orientation/Kconfig"
if IIO_TRIGGER
source "drivers/iio/trigger/Kconfig"
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index 87e4c4369e2f..f9879c29cf6f 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -27,6 +27,7 @@ obj-y += humidity/
obj-y += imu/
obj-y += light/
obj-y += magnetometer/
+obj-y += multiplexer/
obj-y += orientation/
obj-y += potentiometer/
obj-y += pressure/
diff --git a/drivers/iio/multiplexer/Kconfig b/drivers/iio/multiplexer/Kconfig
new file mode 100644
index 000000000000..31cbe4509366
--- /dev/null
+++ b/drivers/iio/multiplexer/Kconfig
@@ -0,0 +1,17 @@
+#
+# Multiplexer drivers
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Multiplexers"
+
+config IIO_MUX
+ tristate "IIO multiplexer driver"
+ depends on OF && MUX_GPIO
+ help
+ Say yes here to build support for the IIO multiplexer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called iio-mux.
+
+endmenu
diff --git a/drivers/iio/multiplexer/Makefile b/drivers/iio/multiplexer/Makefile
new file mode 100644
index 000000000000..68be3c4abd07
--- /dev/null
+++ b/drivers/iio/multiplexer/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for industrial I/O multiplexer drivers
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_IIO_MUX) += iio-mux.o
diff --git a/drivers/iio/multiplexer/iio-mux.c b/drivers/iio/multiplexer/iio-mux.c
new file mode 100644
index 000000000000..6f6644fb9342
--- /dev/null
+++ b/drivers/iio/multiplexer/iio-mux.c
@@ -0,0 +1,313 @@
+/*
+ * IIO multiplexer driver
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+struct mux_child {
+ u32 reg;
+
+ const char **ext_info;
+ int num_ext_info;
+};
+
+struct mux {
+ u32 cache;
+ struct mux_control *control;
+ struct iio_channel *parent;
+ struct iio_dev *indio_dev;
+ struct iio_chan_spec *c;
+ struct mux_child *child;
+};
+
+static int iio_mux_select(struct mux *mux, int idx)
+{
+ struct mux_child *child = &mux->child[idx];
+ int ret;
+ int i;
+
+ ret = mux_control_select(mux->control, child->reg);
+ if (ret < 0)
+ return ret;
+
+ if (mux->cache == child->reg)
+ return 0;
+
+ for (i = 0; i < child->num_ext_info - 1; i += 2) {
+ const char *value = child->ext_info[i + 1];
+
+ ret = iio_write_channel_ext_info(mux->parent,
+ child->ext_info[i],
+ value, strlen(value));
+ if (ret < 0) {
+ mux_control_deselect(mux->control);
+ return ret;
+ }
+ }
+ mux->cache = child->reg;
+
+ return 0;
+}
+
+static void iio_mux_deselect(struct mux *mux)
+{
+ mux_control_deselect(mux->control);
+}
+
+static int mux_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->c;
+ int ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_read_channel_raw(mux->parent, val);
+ break;
+
+ case IIO_CHAN_INFO_SCALE:
+ ret = iio_read_channel_scale(mux->parent, val, val2);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static int mux_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->c;
+ int ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *type = IIO_VAL_INT;
+ ret = iio_read_avail_channel_raw(mux->parent, vals, length);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static int mux_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->c;
+ int ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_write_channel_raw(mux->parent, val);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static const struct iio_info mux_info = {
+ .read_raw = mux_read_raw,
+ .read_avail = mux_read_avail,
+ .write_raw = mux_write_raw,
+ .driver_module = THIS_MODULE,
+};
+
+static int mux_configure_channel(struct device *dev, struct mux *mux,
+ struct device_node *child_np, int idx)
+{
+ struct mux_child *child = &mux->child[idx];
+ struct iio_chan_spec *c = &mux->c[idx];
+ const struct iio_chan_spec *pc = mux->parent->channel;
+ int i;
+ int ret;
+
+ c->indexed = 1;
+ c->channel = idx;
+ c->output = pc->output;
+ c->datasheet_name = child_np->name;
+
+ ret = iio_get_channel_type(mux->parent, &c->type);
+ if (ret < 0) {
+ dev_err(dev, "failed to get parent channel type\n");
+ return ret;
+ }
+
+ if (iio_channel_has_info(pc, IIO_CHAN_INFO_RAW))
+ c->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW);
+ if (iio_channel_has_info(pc, IIO_CHAN_INFO_SCALE))
+ c->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE);
+
+ if (iio_channel_has_available(pc, IIO_CHAN_INFO_RAW))
+ c->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW);
+
+ ret = of_property_read_u32(child_np, "reg", &child->reg);
+ if (ret < 0) {
+ dev_err(dev, "no reg property for node '%s'\n", child_np->name);
+ return ret;
+ }
+
+ for (i = 0; i < idx; ++i) {
+ if (mux->child[i].reg == child->reg) {
+ dev_err(dev, "double use of reg %d\n", child->reg);
+ return -EINVAL;
+ }
+ }
+
+ ret = of_property_read_string_array(child_np, "iio-ext-info", NULL, 0);
+ if (ret <= 0)
+ return 0;
+
+ child->ext_info = devm_kzalloc(dev, sizeof(*child->ext_info) * ret,
+ GFP_KERNEL);
+ if (!child->ext_info)
+ return -ENOMEM;
+
+ child->num_ext_info = ret;
+ ret = of_property_read_string_array(child_np, "iio-ext-info",
+ child->ext_info,
+ child->num_ext_info);
+ if (ret != child->num_ext_info)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int mux_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *child_np;
+ struct iio_dev *indio_dev;
+ struct mux *mux;
+ int children;
+ int sizeof_priv;
+ int idx;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ children = of_get_child_count(np);
+ if (children <= 0) {
+ dev_err(dev, "not even a single child\n");
+ return -EINVAL;
+ }
+
+ sizeof_priv = sizeof(*mux);
+ sizeof_priv += sizeof(*mux->child) * children;
+ sizeof_priv += sizeof(*mux->c) * children;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof_priv);
+ if (!indio_dev)
+ return -ENOMEM;
+
+ mux = iio_priv(indio_dev);
+ mux->child = (struct mux_child *)(mux + 1);
+ mux->c = (struct iio_chan_spec *)(mux->child + children);
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ mux->parent = devm_iio_channel_get(dev, "parent");
+ if (IS_ERR(mux->parent)) {
+ if (PTR_ERR(mux->parent) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get parent channel\n");
+ return PTR_ERR(mux->parent);
+ }
+
+ indio_dev->name = dev_name(dev);
+ indio_dev->dev.parent = dev;
+ indio_dev->info = &mux_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mux->c;
+ indio_dev->num_channels = children;
+
+ idx = 0;
+ for_each_child_of_node(np, child_np) {
+ ret = mux_configure_channel(dev, mux, child_np, idx);
+ if (ret < 0)
+ return ret;
+ idx++;
+ }
+
+ mux->control = devm_mux_control_get(dev, "mux");
+ if (IS_ERR(mux->control)) {
+ if (PTR_ERR(mux->control) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get control-mux\n");
+ return PTR_ERR(mux->control);
+ }
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret) {
+ dev_err(dev, "failed to register iio device\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id mux_match[] = {
+ { .compatible = "iio-mux" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_match);
+
+static struct platform_driver mux_driver = {
+ .probe = mux_probe,
+ .driver = {
+ .name = "iio-mux",
+ .of_match_table = mux_match,
+ },
+};
+module_platform_driver(mux_driver);
+
+MODULE_DESCRIPTION("IIO multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
^ permalink raw reply related
* Клиентские базы Skype: prodawez390 Whatsapp: +79139230330 Viber: +79139230330 Telegram: +79139230330 Email: prodawez391@gmail.com
From: guice@gmx.com @ 2016-11-17 9:53 UTC (permalink / raw)
To: rekl@aisttv.ru
Клиентские базы Skype: prodawez390 Whatsapp: +79139230330 Viber: +79139230330 Telegram: +79139230330 Email: prodawez391@gmail.com
^ permalink raw reply
* Re: [Patch V4 1/2] i2c: imx: add low power i2c bus driver
From: Vladimir Zapolskiy @ 2016-11-17 0:58 UTC (permalink / raw)
To: Gao Pan, wsa, u.kleine-koenig, cmo, robh; +Cc: linux-i2c, frank.li, fugang.duan
In-Reply-To: <1479115411-1537-1-git-send-email-pandy.gao@nxp.com>
On 11/14/2016 11:23 AM, Gao Pan wrote:
> This patch adds lpi2c bus driver to support new i.MX products
> which use lpi2c instead of the old imx i2c.
>
> The lpi2c can continue operating in stop mode when an appropriate
> clock is available. It is also designed for low CPU overhead with
> DMA offloading of FIFO register accesses.
>
> Signed-off-by: Gao Pan <pandy.gao@nxp.com>
> Reviewed-by: Fugang Duan <B38611@freescale.com>
> ---
[snip]
> +
> +static int lpi2c_imx_master_enable(struct lpi2c_imx_struct *lpi2c_imx)
> +{
> + unsigned int temp;
> + int ret;
> +
> + ret = clk_enable(lpi2c_imx->clk);
> + if (ret)
> + return ret;
> +
> + temp = MCR_RST;
> + writel(temp, lpi2c_imx->base + LPI2C_MCR);
> + writel(0, lpi2c_imx->base + LPI2C_MCR);
> +
> + ret = lpi2c_imx_config(lpi2c_imx);
> + if (ret)
> + return ret;
Missing clk_disable() on error path.
> +
> + temp = readl(lpi2c_imx->base + LPI2C_MCR);
> + temp |= MCR_MEN;
> + writel(temp, lpi2c_imx->base + LPI2C_MCR);
> +
> + return 0;
> +}
> +
> +static int lpi2c_imx_master_disable(struct lpi2c_imx_struct *lpi2c_imx)
> +{
> + unsigned int temp = 0;
No need of assignment, just do a declaration "u32 temp;".
> +
> + temp = readl(lpi2c_imx->base + LPI2C_MCR);
> + temp &= ~MCR_MEN;
> + writel(temp, lpi2c_imx->base + LPI2C_MCR);
> +
> + clk_disable(lpi2c_imx->clk);
> +
> + return 0;
> +}
> +
> +static int lpi2c_imx_msg_complete(struct lpi2c_imx_struct *lpi2c_imx)
> +{
> + unsigned int timeout;
unsigned long timeout;
> +
> + timeout = wait_for_completion_timeout(&lpi2c_imx->complete, HZ);
> +
> + return timeout ? 0 : -ETIMEDOUT;
> +}
> +
[snip]
> +static void lpi2c_imx_read_rxfifo(struct lpi2c_imx_struct *lpi2c_imx)
> +{
> + unsigned int blocklen, remaining;
> + unsigned int temp, data;
> +
> + do {
> + data = readl(lpi2c_imx->base + LPI2C_MRDR);
> + if (data & MRDR_RXEMPTY)
> + break;
> +
> + lpi2c_imx->rx_buf[lpi2c_imx->delivered++] = data & 0xff;
> + } while (1);
> +
> + /*
> + * First byte is the length of remaining packet in the SMBus block
> + * data read. Add it to msgs->len.
> + */
> + if (lpi2c_imx->block_data) {
> + blocklen = lpi2c_imx->rx_buf[0];
> + lpi2c_imx->msglen += blocklen;
> + }
> +
> + remaining = lpi2c_imx->msglen - lpi2c_imx->delivered;
> +
> + if (!remaining) {
> + complete(&lpi2c_imx->complete);
> + return;
> + }
> +
> + /* not finished, still waiting for rx data */
> + lpi2c_imx_set_rx_watermark(lpi2c_imx);
> +
> + /* multiple receive commands */
> + if (lpi2c_imx->block_data) {
> + lpi2c_imx->block_data = 0;
> + temp = remaining;
> + temp |= (RECV_DATA << 8);
> + writel(temp, lpi2c_imx->base + LPI2C_MTDR);
> + } else if (!(lpi2c_imx->delivered & 0xff)) {
> + temp = remaining > CHUNK_DATA ? CHUNK_DATA - 1 : (remaining - 1);
checkpatch complains:
WARNING: line over 80 characters
#473: FILE: drivers/i2c/busses/i2c-imx-lpi2c.c:421:
+ temp = remaining > CHUNK_DATA ? CHUNK_DATA - 1 : (remaining - 1);
May be rewrite it as
temp = (remaining > CHUNK_DATA ? CHUNK_DATA : remaining) - 1;
?
> + temp |= (RECV_DATA << 8);
> + writel(temp, lpi2c_imx->base + LPI2C_MTDR);
> + }
> +
> + lpi2c_imx_intctrl(lpi2c_imx, MIER_RDIE);
> +}
> +
[snip]
> +
> +static u32 lpi2c_imx_func(struct i2c_adapter *adapter)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
> + I2C_FUNC_SMBUS_READ_BLOCK_DATA;
Please indent deeper the second line.
> +}
> +
> +static struct i2c_algorithm lpi2c_imx_algo = {
> + .master_xfer = lpi2c_imx_xfer,
> + .functionality = lpi2c_imx_func,
> +};
> +
> +static const struct of_device_id lpi2c_imx_of_match[] = {
> + { .compatible = "fsl,imx8dv-lpi2c" },
> + { .compatible = "fsl,imx7ulp-lpi2c" },
Please sort the compatibles alphanumerically.
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, lpi2c_imx_of_match)
Missing closing ";" ^^^^^^
> +
> +static int lpi2c_imx_probe(struct platform_device *pdev)
> +{
> + struct lpi2c_imx_struct *lpi2c_imx;
> + struct resource *res;
> + int irq, ret;
> +
> + lpi2c_imx = devm_kzalloc(&pdev->dev,
> + sizeof(*lpi2c_imx), GFP_KERNEL);
Please place it on a single line, it fits into 80 character limit.
> + if (!lpi2c_imx)
> + return -ENOMEM;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + lpi2c_imx->base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(lpi2c_imx->base))
> + return PTR_ERR(lpi2c_imx->base);
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(&pdev->dev, "can't get irq number\n");
> + return irq;
> + }
> +
> + lpi2c_imx->adapter.owner = THIS_MODULE;
> + lpi2c_imx->adapter.algo = &lpi2c_imx_algo;
> + lpi2c_imx->adapter.dev.parent = &pdev->dev;
> + lpi2c_imx->adapter.dev.of_node = pdev->dev.of_node;
> + strlcpy(lpi2c_imx->adapter.name, pdev->name,
> + sizeof(lpi2c_imx->adapter.name));
checkpatch --strict complains:
CHECK: Alignment should match open parenthesis
#630: FILE: drivers/i2c/busses/i2c-imx-lpi2c.c:578:
+ strlcpy(lpi2c_imx->adapter.name, pdev->name,
+ sizeof(lpi2c_imx->adapter.name));
> +
> + lpi2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
> + if (IS_ERR(lpi2c_imx->clk)) {
> + dev_err(&pdev->dev, "can't get I2C peripheral clock\n");
> + return PTR_ERR(lpi2c_imx->clk);
> + }
> +
> + ret = clk_prepare(lpi2c_imx->clk);
> + if (ret) {
> + dev_err(&pdev->dev, "clk prepare failed %d\n", ret);
> + return ret;
> + }
> +
> + ret = of_property_read_u32(pdev->dev.of_node,
> + "clock-frequency", &lpi2c_imx->bitrate);
> + if (ret)
> + lpi2c_imx->bitrate = LPI2C_DEFAULT_RATE;
> +
> + ret = devm_request_irq(&pdev->dev, irq, lpi2c_imx_isr, 0,
> + pdev->name, lpi2c_imx);
> + if (ret) {
> + dev_err(&pdev->dev, "can't claim irq %d\n", irq);
> + return ret;
On error path you leak clk_prepare()'d lpi2c_imx->clk clock.
I would recommend to prepare the clock after successful
adapter registration.
> + }
> +
> + i2c_set_adapdata(&lpi2c_imx->adapter, lpi2c_imx);
> + platform_set_drvdata(pdev, lpi2c_imx);
> +
> + ret = i2c_add_adapter(&lpi2c_imx->adapter);
> + if (ret) {
> + dev_err(&pdev->dev, "registration failed\n");
Please remove this notification line, it is reported by I2C core.
> + return ret;
> + }
> +
> + dev_info(&lpi2c_imx->adapter.dev, "LPI2C adapter registered\n");
> +
> + return 0;
> +}
> +
> +static int lpi2c_imx_remove(struct platform_device *pdev)
> +{
> + struct lpi2c_imx_struct *lpi2c_imx = platform_get_drvdata(pdev);
> +
Missing clk_unprepare(lpi2c_imx->clk clock);
> + i2c_del_adapter(&lpi2c_imx->adapter);
> +
> + return 0;
> +}
> +
> +static struct platform_driver lpi2c_imx_driver = {
> + .probe = lpi2c_imx_probe,
> + .remove = lpi2c_imx_remove,
> + .driver = {
> + .name = DRIVER_NAME,
> + .of_match_table = lpi2c_imx_of_match,
> + },
> +};
> +
> +module_platform_driver(lpi2c_imx_driver);
> +
> +MODULE_AUTHOR("Gao Pan <pandy.gao@nxp.com>");
> +MODULE_DESCRIPTION("I2C adapter driver for LPI2C bus");
> +MODULE_LICENSE("GPL v2");
>
--
With best wishes,
Vladimir
^ permalink raw reply
* Re: [Patch V4 2/2] i2c: imx: add devicetree binding for lpi2c
From: Vladimir Zapolskiy @ 2016-11-17 0:31 UTC (permalink / raw)
To: Gao Pan, wsa, u.kleine-koenig, cmo, robh; +Cc: linux-i2c, frank.li, fugang.duan
In-Reply-To: <1479115411-1537-2-git-send-email-pandy.gao@nxp.com>
On 11/14/2016 11:23 AM, Gao Pan wrote:
> Add a binding document for lpi2c driver
>
> Signed-off-by: Gao Pan <pandy.gao@nxp.com>
> ---
> .../devicetree/bindings/i2c/i2c-imx-lpi2c.txt | 20 ++++++++++++++++++++
> 1 file changed, 20 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-imx-lpi2c.txt b/Documentation/devicetree/bindings/i2c/i2c-imx-lpi2c.txt
> new file mode 100644
> index 0000000..edaf04f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-imx-lpi2c.txt
> @@ -0,0 +1,20 @@
> +* Freescale Low Power Inter IC (LPI2C) for i.MX
> +
> +Required properties:
> +- compatible :
> + - "fsl,imx8dv-lpi2c" for LPI2C compatible with the one integrated on i.MX8DV soc
> + - "fsl,imx7ulp-lpi2c" for LPI2C compatible with the one integrated on i.MX7ULP soc
> +- reg : address and length of the lpi2c master registers
> +- interrupt-parent : core interrupt controller
> +- interrupts : lpi2c interrupt
> +- clocks : lpi2c clock specifier
I don't know if description of standard properties is really needed,
probably Rob can advise.
My point is that pretty "interrupts-extended" is never mentioned, and
all three interrupt* properties are well described in interrupts.txt,
and in general there is no point to mention all those interrupts,
clocks, clock-names and friends properties again and again.
But here better to get a clarification from Rob.
> +
> +Examples:
> +
> +lpi2c7: lpi2c7@40A50000 {
> + compatible = "fsl,imx8dv-lpi2c";
> + reg = <0x40A50000 0x10000>;
> + interrupt-parent = <&intc>;
> + interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clks IMX7ULP_CLK_LPI2C7>;
> +};
>
Please rearrange the changes, devicetree binding change should be 1/2,
the driver change should be 2/2.
--
With best wishes,
Vladimir
^ permalink raw reply
* Re: [patch v5 repost 1/1] i2c: add master driver for mellanox systems
From: Vladimir Zapolskiy @ 2016-11-17 0:20 UTC (permalink / raw)
To: vadimp, wsa; +Cc: linux-i2c, linux-kernel, jiri, Michael Shych
In-Reply-To: <1479280122-109711-1-git-send-email-vadimp@mellanox.com>
Hi Vadim,
I've noticed two more minor issues, please consider to fix them.
On 11/16/2016 09:08 AM, vadimp@mellanox.com wrote:
> From: Vadim Pasternak <vadimp@mellanox.com>
>
> Device driver for Mellanox I2C controller logic, implemented in Lattice
> CPLD device.
> Device supports:
> - Master mode
> - One physical bus
> - Polling mode
>
> The Kconfig currently controlling compilation of this code is:
> drivers/i2c/busses/Kconfig:config I2C_MLXCPLD
>
> Signed-off-by: Michael Shych <michaelsh@mellanox.com>
> Signed-off-by: Vadim Pasternak <vadimp@mellanox.com>
> Reviewed-by: Jiri Pirko <jiri@mellanox.com>
> Reviewed-by: Vladimir Zapolskiy <vz@mleia.com>
Still valid.
[snip]
> diff --git a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c
> new file mode 100644
> index 0000000..f7a6f3a
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-mlxcpld.c
> @@ -0,0 +1,551 @@
> +/*
> + * drivers/i2c/busses/i2c-mlxcpld.c
You can drop this line with the path above.
> + * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
> + * Copyright (c) 2016 Michael Shych <michaels@mellanox.com>
> + *
[snip]
> +
> +/**
> + * struct mlxcpld_i2c_priv - private controller data:
> + * @lpc_gen_dec_reg: register space;
This field is gone, right?
> + * @adap: i2c adapter;
> + * @dev_id: device id;
> + * @base_addr: base IO address;
> + * @lock: bus access lock;
> + * @xfer: current i2c transfer block;
> + * @dev: device handle;
> + */
> +
> +struct mlxcpld_i2c_priv {
> + struct i2c_adapter adap;
> + u16 dev_id;
This field is unused, right?
> + u16 base_addr;
For in[bwl]/out[bwl] IO addr has u32 type, it might be better to have
it u32 here as well.
> + struct mutex lock;
> + struct mlxcpld_i2c_curr_transf xfer;
> + struct device *dev;
> +};
> +
[snip]
> +
> +/* Check validity of current i2c message and all transfer.
> + * Calculate also coomon length of all i2c messages in transfer.
Typo, s/coomon/common/
> + */
> +static int mlxcpld_i2c_invalid_len(struct mlxcpld_i2c_priv *priv,
> + const struct i2c_msg *msg, u8 *comm_len)
[snip]
> +
> +/* Make sure the CPLD is ready to start transmitting.
> + * Return 0 if it is, -EBUSY if it is not.
> + */
While it has no runtime effect, the function returns -EIO if bus is
busy. If you add a comment (by the way you may consider to remove it),
please make it aligned with the function.
> +static int mlxcpld_i2c_check_busy(struct mlxcpld_i2c_priv *priv)
> +{
> + u8 val;
> +
> + mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_STATUS_REG, &val, 1);
> +
> + if (val & MLXCPLD_LPCI2C_TRANS_END)
> + return 0;
> +
> + return -EIO;
> +}
> +
[snip]
> +
> +static int mlxcpld_i2c_probe(struct platform_device *pdev)
> +{
> + struct mlxcpld_i2c_priv *priv;
> + int err;
> +
> + priv = devm_kzalloc(&pdev->dev, sizeof(struct mlxcpld_i2c_priv),
> + GFP_KERNEL);
Common practice to avoid hypothetical problems in future after struct
renamings etc. is to get sizeof() of the target storage, here it
should be changed to:
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
There is no dereferencing, so it is a safe expression.
> + if (!priv)
> + return -ENOMEM;
> +
> + mutex_init(&priv->lock);
> + platform_set_drvdata(pdev, priv);
> +
> + priv->dev = &pdev->dev;
> +
> + /* Register with i2c layer */
> + mlxcpld_i2c_adapter.timeout = usecs_to_jiffies(MLXCPLD_I2C_XFER_TO);
> + priv->adap = mlxcpld_i2c_adapter;
> + priv->adap.dev.parent = &pdev->dev;
> + priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADDR;
> + i2c_set_adapdata(&priv->adap, priv);
> +
> + err = i2c_add_numbered_adapter(&priv->adap);
> + if (err)
> + mutex_destroy(&priv->lock);
> +
> + return err;
> +}
--
With best wishes,
Vladimir
^ permalink raw reply
* [RFC PATCH 1/7] dt-bindings: document devicetree bindings for mux-gpio
From: Peter Rosin @ 2016-11-16 23:48 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Arnd Bergmann, Greg Kroah-Hartman,
linux-i2c, devicetree, linux-iio
In-Reply-To: <1479340111-1259-1-git-send-email-peda@axentia.se>
---
.../devicetree/bindings/misc/mux-gpio.txt | 76 ++++++++++++++++++++++
1 file changed, 76 insertions(+)
create mode 100644 Documentation/devicetree/bindings/misc/mux-gpio.txt
diff --git a/Documentation/devicetree/bindings/misc/mux-gpio.txt b/Documentation/devicetree/bindings/misc/mux-gpio.txt
new file mode 100644
index 000000000000..8c1745ecd4ff
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/mux-gpio.txt
@@ -0,0 +1,76 @@
+GPIO-based multiplexer controller bindings
+
+Define what GPIO pins are used to control a multiplexer. Or several
+multiplexers, if the same pins control more than one multiplexer.
+
+Required properties:
+- compatible : "mux-gpio"
+- mux-gpios : list of gpios used to control the multiplexer, least
+ significant bit first.
+
+Example:
+ control_mux: control-adc-mux {
+ compatible = "mux-gpio";
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "iio-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ control-muxes = <&control_mux>;
+ control-mux-names = "mux";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ sync-1@0 {
+ reg = <0>;
+ };
+
+ in@1 {
+ reg = <1>;
+ };
+
+ out@2 {
+ reg = <2>;
+ };
+
+ sync-2@3 {
+ reg = <3>;
+ };
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux-simple,mux-locked";
+ i2c-parent = <&i2c1>;
+
+ control-muxes = <&control_mux>;
+ control-mux-names = "mux";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ssd1307: oled@3c {
+ /* ... */
+ };
+ };
+
+ i2c@3 {
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pca9555: pca9555@20 {
+ /* ... */
+ };
+ };
+ };
--
2.1.4
^ permalink raw reply related
* [RFC PATCH 7/7] i2c: i2c-mux-simple: new driver
From: Peter Rosin @ 2016-11-16 23:48 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Arnd Bergmann, Greg Kroah-Hartman,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1479340111-1259-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
This is a generic simple i2c mux that uses the generic multiplexer
subsystem to do the muxing.
The user can select if the mux is to be mux-locked and parent-locked
as described in Documentation/i2c/i2c-topology.
---
drivers/i2c/muxes/Kconfig | 12 +++
drivers/i2c/muxes/Makefile | 1 +
drivers/i2c/muxes/i2c-mux-simple.c | 172 +++++++++++++++++++++++++++++++++++++
3 files changed, 185 insertions(+)
create mode 100644 drivers/i2c/muxes/i2c-mux-simple.c
diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig
index e280c8ecc0b5..45e80ad2d4ab 100644
--- a/drivers/i2c/muxes/Kconfig
+++ b/drivers/i2c/muxes/Kconfig
@@ -72,6 +72,18 @@ config I2C_MUX_REG
This driver can also be built as a module. If so, the module
will be called i2c-mux-reg.
+config I2C_MUX_SIMPLE
+ tristate "Simple I2C multiplexer"
+ depends on OF
+ help
+ If you say yes to this option, support will be included for a
+ simple generic I2C multiplexer. This driver provides access to
+ I2C busses connected through a MUX, which is controlled
+ by a generic MUX controller.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-mux-simple.
+
config I2C_DEMUX_PINCTRL
tristate "pinctrl-based I2C demultiplexer"
depends on PINCTRL && OF
diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile
index 7c267c29b191..eea1fb9e6466 100644
--- a/drivers/i2c/muxes/Makefile
+++ b/drivers/i2c/muxes/Makefile
@@ -10,5 +10,6 @@ obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o
obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o
obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o
obj-$(CONFIG_I2C_MUX_REG) += i2c-mux-reg.o
+obj-$(CONFIG_I2C_MUX_SIMPLE) += i2c-mux-simple.o
ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
diff --git a/drivers/i2c/muxes/i2c-mux-simple.c b/drivers/i2c/muxes/i2c-mux-simple.c
new file mode 100644
index 000000000000..308ecbf9d66c
--- /dev/null
+++ b/drivers/i2c/muxes/i2c-mux-simple.c
@@ -0,0 +1,172 @@
+/*
+ * Generic simple I2C multiplexer
+ *
+ * Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+struct mux {
+ struct mux_control *control;
+
+ int parent;
+ int n_values;
+ u32 *values;
+};
+
+static int i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
+{
+ struct mux *mux = i2c_mux_priv(muxc);
+
+ return mux_control_select(mux->control, chan);
+}
+
+static int i2c_mux_deselect(struct i2c_mux_core *muxc, u32 chan)
+{
+ struct mux *mux = i2c_mux_priv(muxc);
+
+ return mux_control_deselect(mux->control);
+}
+
+static int i2c_mux_probe_dt(struct mux *mux,
+ struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *adapter_np, *child;
+ struct i2c_adapter *adapter;
+ int i = 0;
+
+ if (!np)
+ return -ENODEV;
+
+ adapter_np = of_parse_phandle(np, "i2c-parent", 0);
+ if (!adapter_np) {
+ dev_err(dev, "Cannot parse i2c-parent\n");
+ return -ENODEV;
+ }
+ adapter = of_find_i2c_adapter_by_node(adapter_np);
+ of_node_put(adapter_np);
+ if (!adapter)
+ return -EPROBE_DEFER;
+
+ mux->parent = i2c_adapter_id(adapter);
+ put_device(&adapter->dev);
+
+ mux->control = devm_mux_control_get(dev, "mux");
+ if (IS_ERR(mux->control)) {
+ if (PTR_ERR(mux->control) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get control-mux\n");
+ return PTR_ERR(mux->control);
+ }
+
+ mux->n_values = of_get_child_count(np);
+
+ mux->values = devm_kzalloc(dev,
+ sizeof(*mux->values) * mux->n_values,
+ GFP_KERNEL);
+ if (!mux->values)
+ return -ENOMEM;
+
+ for_each_child_of_node(np, child) {
+ of_property_read_u32(child, "reg", mux->values + i);
+ i++;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id i2c_mux_of_match[] = {
+ { .compatible = "i2c-mux-simple,parent-locked",
+ .data = (void *)0, },
+ { .compatible = "i2c-mux-simple,mux-locked",
+ .data = (void *)1, },
+ {},
+};
+MODULE_DEVICE_TABLE(of, i2c_mux_of_match);
+
+static int i2c_mux_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct i2c_mux_core *muxc;
+ struct mux *mux;
+ struct i2c_adapter *parent;
+ int i, ret;
+
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return -ENOMEM;
+
+ ret = i2c_mux_probe_dt(mux, dev);
+ if (ret < 0)
+ return ret;
+
+ parent = i2c_get_adapter(mux->parent);
+ if (!parent)
+ return -EPROBE_DEFER;
+
+ muxc = i2c_mux_alloc(parent, dev, mux->n_values, 0, 0,
+ i2c_mux_select, i2c_mux_deselect);
+ if (!muxc) {
+ ret = -ENOMEM;
+ goto alloc_failed;
+ }
+ muxc->priv = mux;
+
+ platform_set_drvdata(pdev, muxc);
+
+ match = of_match_device(of_match_ptr(i2c_mux_of_match), dev);
+ if (match)
+ muxc->mux_locked = !!of_device_get_match_data(dev);
+
+ for (i = 0; i < mux->n_values; i++) {
+ ret = i2c_mux_add_adapter(muxc, 0, mux->values[i], 0);
+ if (ret)
+ goto add_adapter_failed;
+ }
+
+ dev_info(dev, "%d port mux on %s adapter\n",
+ mux->n_values, parent->name);
+
+ return 0;
+
+add_adapter_failed:
+ i2c_mux_del_adapters(muxc);
+alloc_failed:
+ i2c_put_adapter(parent);
+
+ return ret;
+}
+
+static int i2c_mux_remove(struct platform_device *pdev)
+{
+ struct i2c_mux_core *muxc = platform_get_drvdata(pdev);
+
+ i2c_mux_del_adapters(muxc);
+ i2c_put_adapter(muxc->parent);
+
+ return 0;
+}
+
+static struct platform_driver i2c_mux_driver = {
+ .probe = i2c_mux_probe,
+ .remove = i2c_mux_remove,
+ .driver = {
+ .name = "i2c-mux-simple",
+ .of_match_table = i2c_mux_of_match,
+ },
+};
+module_platform_driver(i2c_mux_driver);
+
+MODULE_DESCRIPTION("Simple I2C multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
^ permalink raw reply related
* [RFC PATCH 6/7] dt-bindings: i2c: i2c-mux-simple: document i2c-mux-simple bindings
From: Peter Rosin @ 2016-11-16 23:48 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Arnd Bergmann, Greg Kroah-Hartman,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1479340111-1259-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
.../devicetree/bindings/i2c/i2c-mux-simple.txt | 91 ++++++++++++++++++++++
1 file changed, 91 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
diff --git a/Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt b/Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
new file mode 100644
index 000000000000..fec1ab39ad64
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
@@ -0,0 +1,91 @@
+Simple I2C Bus Mux
+
+This binding describes an I2C bus multiplexer that uses GPIOs to
+route the I2C signals.
+
+ .-----. .-----.
+ | dev | | dev |
+ .------------. '-----' '-----'
+ | SoC | | |
+ | | .--------+--------'
+ | .------. | .------+ child bus A, on MUX value set to 0
+ | | I2C |-|--| Mux |
+ | '------' | '--+---+ child bus B, on MUX value set to 1
+ | .------. | | '----------+--------+--------.
+ | | MUX- | | | | | |
+ | | Ctrl |-|-----+ .-----. .-----. .-----.
+ | '------' | | dev | | dev | | dev |
+ '------------' '-----' '-----' '-----'
+
+Required properties:
+- compatible: i2c-mux-simple,mux-locked or i2c-mux-simple,parent-locked
+- i2c-parent: The phandle of the I2C bus that this multiplexer's master-side
+ port is connected to.
+- control-muxes : Node of the multiplexer that controls "Mux".
+- control-mux-names : Should be "mux".
+* Standard I2C mux properties. See i2c-mux.txt in this directory.
+* I2C child bus nodes. See i2c-mux.txt in this directory.
+
+Optional properties:
+- idle-state: value to set the muxer to when idle. When no value is
+ given, it defaults to the last value used.
+
+For each i2c child node, an I2C child bus will be created. They will
+be numbered based on their order in the device tree.
+
+Whenever an access is made to a device on a child bus, the value set
+in the relevant node's reg property will be fed to the mux controller.
+
+If an idle state is defined, using the idle-state (optional) property,
+whenever an access is not being made to a device on a child bus, the
+mux controller will be set according to the idle value.
+
+If an idle state is not defined, the most recently used value will be
+left programmed into hardware whenever no access is being made to a
+device on a child bus.
+
+Example:
+ control_i2c_mux: control-i2c-mux {
+ compatible = "mux-gpio";
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux-simple,mux-locked";
+ i2c-parent = <&i2c1>;
+
+ control-muxes = <&control_i2c_mux>;
+ control-mux-names = "mux";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@1 {
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ssd1307: oled@3c {
+ compatible = "solomon,ssd1307fb-i2c";
+ reg = <0x3c>;
+ pwms = <&pwm 4 3000>;
+ reset-gpios = <&gpio2 7 1>;
+ reset-active-low;
+ };
+ };
+
+ i2c@3 {
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pca9555: pca9555@20 {
+ compatible = "nxp,pca9555";
+ gpio-controller;
+ #gpio-cells = <2>;
+ reg = <0x20>;
+ };
+ };
+ };
--
2.1.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [RFC PATCH 4/7] dt-bindings: iio: iio-mux: document iio-mux bindings
From: Peter Rosin @ 2016-11-16 23:48 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Arnd Bergmann, Greg Kroah-Hartman,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1479340111-1259-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
.../bindings/iio/multiplexer/iio-mux.txt | 59 ++++++++++++++++++++++
1 file changed, 59 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
diff --git a/Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt b/Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
new file mode 100644
index 000000000000..2f5c7fc35a42
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
@@ -0,0 +1,59 @@
+IIO multiplexer bindings
+
+If a multiplexer is used to select when hardware signal is fed to
+e.g. an ADC channel, these bindings describe that situation.
+
+Required properties:
+- compatible : "iio-mux"
+- io-channels : Channel node of the parent channel that has multiplexed
+ input.
+- io-channel-names : Should be "parent".
+- control-muxes : Node of the multiplexer that controls the input signal.
+- control-mux-names : Should be "mux".
+- #address-cells = <1>;
+- #size-cells = <0>;
+
+Required properties for iio-mux child nodes:
+- reg : The multiplexer number.
+
+Optional properties for iio-mux child nodes:
+- iio-ext-info : Array of string pairs, the first item in each pair is the
+ iio ext_info attribute name, and the second item in each
+ pair is the iio ext_info value for that attribute. The
+ mux will write these ext_info values to the corresponding
+ ext_info attributes on every multiplexer switch.
+
+Example:
+ control_adc_mux: control-adc-mux {
+ compatible = "mux-gpio";
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "iio-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ control-muxes = <&control_adc_mux>;
+ control-mux-names = "mux";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ sync@0 {
+ reg = <0>;
+ iio-ext-info = "invert", "1";
+ };
+
+ in@1 {
+ reg = <1>;
+ iio-ext-info = "invert", "1";
+ };
+
+ system-regulator@2 {
+ reg = <2>;
+ iio-ext-info = "invert", "0";
+ };
+ };
--
2.1.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [RFC PATCH 3/7] iio: inkern: api for manipulating ext_info of iio channels
From: Peter Rosin @ 2016-11-16 23:48 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Arnd Bergmann, Greg Kroah-Hartman,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1479340111-1259-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
Extend the inkern api with functions for reading and writing ext_info
of iio channels.
---
drivers/iio/inkern.c | 40 ++++++++++++++++++++++++++++++++++++++++
include/linux/iio/consumer.h | 5 +++++
2 files changed, 45 insertions(+)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index cfca17ba2535..5348735281ee 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -850,3 +850,43 @@ int iio_write_channel_raw(struct iio_channel *chan, int val)
return ret;
}
EXPORT_SYMBOL_GPL(iio_write_channel_raw);
+
+int iio_read_channel_ext_info(struct iio_channel *chan,
+ const char *attr, char *buf)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+
+ if (!chan->channel->ext_info)
+ return -EINVAL;
+
+ for (ext_info = chan->channel->ext_info; ext_info->name; ++ext_info) {
+ if (strcmp(attr, ext_info->name))
+ continue;
+
+ return ext_info->read(chan->indio_dev, ext_info->private,
+ chan->channel, buf);
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(iio_read_channel_ext_info);
+
+int iio_write_channel_ext_info(struct iio_channel *chan,
+ const char *attr, const char *buf, size_t len)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+
+ if (!chan->channel->ext_info)
+ return -EINVAL;
+
+ for (ext_info = chan->channel->ext_info; ext_info->name; ++ext_info) {
+ if (strcmp(attr, ext_info->name))
+ continue;
+
+ return ext_info->write(chan->indio_dev, ext_info->private,
+ chan->channel, buf, len);
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(iio_write_channel_ext_info);
diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
index 9a4f336d8b4a..3859981cea0a 100644
--- a/include/linux/iio/consumer.h
+++ b/include/linux/iio/consumer.h
@@ -299,4 +299,9 @@ int iio_read_channel_scale(struct iio_channel *chan, int *val,
int iio_convert_raw_to_processed(struct iio_channel *chan, int raw,
int *processed, unsigned int scale);
+int iio_read_channel_ext_info(struct iio_channel *chan,
+ const char *attr, char *buf);
+int iio_write_channel_ext_info(struct iio_channel *chan,
+ const char *attr, const char *buf, size_t len);
+
#endif
--
2.1.4
^ permalink raw reply related
* [RFC PATCH 2/7] misc: minimal mux subsystem and gpio-based mux controller
From: Peter Rosin @ 2016-11-16 23:48 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Arnd Bergmann, Greg Kroah-Hartman,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1479340111-1259-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
When both the iio subsystem and the i2c subsystem wants to update
the same mux, there needs to be some coordination. Invent a new
minimal "mux" subsystem that handles this.
Add a single backend driver for this new subsystem that can
control gpio based multiplexers.
---
drivers/misc/Kconfig | 6 +
drivers/misc/Makefile | 2 +
drivers/misc/mux-core.c | 285 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/misc/mux-gpio.c | 105 ++++++++++++++++++
include/linux/mux.h | 54 +++++++++
5 files changed, 452 insertions(+)
create mode 100644 drivers/misc/mux-core.c
create mode 100644 drivers/misc/mux-gpio.c
create mode 100644 include/linux/mux.h
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 64971baf11fa..9e119bb78d82 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -766,6 +766,12 @@ config PANEL_BOOT_MESSAGE
An empty message will only clear the display at driver init time. Any other
printf()-formatted message is valid with newline and escape codes.
+config MUX_GPIO
+ tristate "GPIO-controlled MUX controller"
+ depends on OF
+ help
+ GPIO-controlled MUX controller
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 31983366090a..92b547bcbac1 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -53,6 +53,8 @@ obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
obj-$(CONFIG_PANEL) += panel.o
+obj-$(CONFIG_MUX_GPIO) += mux-core.o
+obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o
diff --git a/drivers/misc/mux-core.c b/drivers/misc/mux-core.c
new file mode 100644
index 000000000000..d7c7b667d1a4
--- /dev/null
+++ b/drivers/misc/mux-core.c
@@ -0,0 +1,285 @@
+/*
+ * Multiplexer subsystem
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) "mux-core: " fmt
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+static struct bus_type mux_bus_type = {
+ .name = "mux",
+};
+
+static int __init mux_init(void)
+{
+ return bus_register(&mux_bus_type);
+}
+
+static void __exit mux_exit(void)
+{
+ bus_unregister(&mux_bus_type);
+}
+
+static DEFINE_IDA(mux_ida);
+
+static void mux_control_release(struct device *dev)
+{
+ struct mux_control *mux = to_mux_control(dev);
+
+ ida_simple_remove(&mux_ida, mux->id);
+ kfree(mux);
+}
+
+static struct device_type mux_control_type = {
+ .name = "mux-control",
+ .release = mux_control_release,
+};
+
+/*
+ * Allocate a mux-control, plus an extra memory area for private use
+ * by the caller.
+ */
+struct mux_control *mux_control_alloc(size_t sizeof_priv)
+{
+ struct mux_control *mux;
+
+ mux = kzalloc(sizeof(*mux) + sizeof_priv, GFP_KERNEL);
+ if (!mux)
+ return NULL;
+
+ mux->dev.bus = &mux_bus_type;
+ mux->dev.type = &mux_control_type;
+ device_initialize(&mux->dev);
+ dev_set_drvdata(&mux->dev, mux);
+
+ init_rwsem(&mux->lock);
+ mux->priv = mux + 1;
+
+ mux->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
+ if (mux->id < 0) {
+ pr_err("mux-controlX failed to get device id\n");
+ kfree(mux);
+ return NULL;
+ }
+ dev_set_name(&mux->dev, "mux:control%d", mux->id);
+
+ return mux;
+}
+EXPORT_SYMBOL_GPL(mux_control_alloc);
+
+/*
+ * Register the mux-control, thus readying it for use.
+ */
+int mux_control_register(struct mux_control *mux)
+{
+ /* If the calling driver did not initialize of_node, do it here */
+ if (!mux->dev.of_node && mux->dev.parent)
+ mux->dev.of_node = mux->dev.parent->of_node;
+
+ return device_add(&mux->dev);
+}
+EXPORT_SYMBOL_GPL(mux_control_register);
+
+/*
+ * Take the mux-control off-line.
+ */
+void mux_control_unregister(struct mux_control *mux)
+{
+ device_del(&mux->dev);
+}
+EXPORT_SYMBOL_GPL(mux_control_unregister);
+
+/*
+ * Put away the mux-control for good.
+ */
+void mux_control_put(struct mux_control *mux)
+{
+ if (!mux)
+ return;
+ put_device(&mux->dev);
+}
+EXPORT_SYMBOL_GPL(mux_control_free);
+
+/*
+ * Select the given multiplexer channel. Call mux_control_deselect()
+ * when the operation is complete on the multiplexer channel, and the
+ * multiplexer is free for others to use.
+ */
+int mux_control_select(struct mux_control *mux, int reg)
+{
+ int ret;
+
+ if (down_read_trylock(&mux->lock)) {
+ if (mux->cache == reg)
+ return 0;
+
+ /* Sigh, the mux needs updating... */
+ up_read(&mux->lock);
+ }
+
+ /* ...or it's just contended. */
+ down_write(&mux->lock);
+
+ if (mux->cache == reg) {
+ /*
+ * Hmmm, someone else changed the mux to my liking.
+ * That makes me wonder how long I waited for nothing...
+ */
+ downgrade_write(&mux->lock);
+ return 0;
+ }
+
+ ret = mux->ops->set(mux, reg);
+ if (ret < 0) {
+ up_write(&mux->lock);
+ return ret;
+ }
+
+ mux->cache = reg;
+ downgrade_write(&mux->lock);
+
+ return 1;
+}
+EXPORT_SYMBOL_GPL(mux_control_select);
+
+/*
+ * Deselect the previously selected multiplexer channel.
+ */
+int mux_control_deselect(struct mux_control *mux)
+{
+ if (mux->do_idle && mux->cache != mux->idle_state) {
+ mux->ops->set(mux, mux->idle_state);
+ mux->cache = mux->idle_state;
+ }
+
+ up_read(&mux->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mux_control_deselect);
+
+static int of_dev_node_match(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+static struct mux_control *of_find_mux_by_node(struct device_node *np)
+{
+ struct device *dev;
+
+ dev = bus_find_device(&mux_bus_type, NULL, np, of_dev_node_match);
+
+ return dev ? to_mux_control(dev) : NULL;
+}
+
+static struct mux_control *of_mux_control_get(struct device_node *np, int index)
+{
+ struct device_node *mux_np;
+ struct mux_control *mux;
+
+ mux_np = of_parse_phandle(np, "control-muxes", index);
+ if (!mux_np)
+ return NULL;
+
+ mux = of_find_mux_by_node(mux_np);
+ of_node_put(mux_np);
+
+ return mux;
+}
+
+/*
+ * Get a named mux.
+ */
+struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
+{
+ struct device_node *np = dev->of_node;
+ struct mux_control *mux;
+ int index;
+
+ index = of_property_match_string(np, "control-mux-names", mux_name);
+ if (index < 0) {
+ dev_err(dev, "failed to get control-mux %s:%s(%i)\n",
+ np->full_name, mux_name ?: "", index);
+ return ERR_PTR(index);
+ }
+
+ mux = of_mux_control_get(np, index);
+ if (!mux)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return mux;
+}
+EXPORT_SYMBOL_GPL(mux_control_get);
+
+static void devm_mux_control_free(struct device *dev, void *res)
+{
+ struct mux_control *mux = *(struct mux_control **)res;
+
+ mux_control_put(mux);
+}
+
+/*
+ * Get a named mux, with resource management.
+ */
+struct mux_control *devm_mux_control_get(struct device *dev,
+ const char *mux_name)
+{
+ struct mux_control **ptr, *mux;
+
+ ptr = devres_alloc(devm_mux_control_free, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ mux = mux_control_get(dev, mux_name);
+ if (IS_ERR(mux)) {
+ devres_free(ptr);
+ return mux;
+ }
+
+ *ptr = mux;
+ devres_add(dev, ptr);
+
+ return mux;
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_get);
+
+static int devm_mux_control_match(struct device *dev, void *res, void *data)
+{
+ struct mux_control **r = res;
+
+ if (!r || !*r) {
+ WARN_ON(!r || !*r);
+ return 0;
+ }
+
+ return *r == data;
+}
+
+/*
+ * Resource-managed version mux_control_put.
+ */
+void devm_mux_control_put(struct device *dev, struct mux_control *mux)
+{
+ WARN_ON(devres_release(dev, devm_mux_control_free,
+ devm_mux_control_match, mux));
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_put);
+
+subsys_initcall(mux_init);
+module_exit(mux_exit);
+
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org");
+MODULE_DESCRIPTION("MUX subsystem");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/misc/mux-gpio.c b/drivers/misc/mux-gpio.c
new file mode 100644
index 000000000000..5fa59a0c49cf
--- /dev/null
+++ b/drivers/misc/mux-gpio.c
@@ -0,0 +1,105 @@
+/*
+ * GPIO-controlled multiplexer driver
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+struct mux_gpio {
+ struct gpio_descs *gpios;
+};
+
+static int mux_gpio_set(struct mux_control *mux, int val)
+{
+ struct mux_gpio *mux_gpio = mux->priv;
+ int i;
+
+ for (i = 0; i < mux_gpio->gpios->ndescs; i++)
+ gpiod_set_value_cansleep(mux_gpio->gpios->desc[i],
+ val & (1 << i));
+
+ return 0;
+}
+
+static const struct mux_control_ops mux_gpio_ops = {
+ .set = mux_gpio_set,
+};
+
+static const struct of_device_id mux_gpio_dt_ids[] = {
+ { .compatible = "mux-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
+
+static int mux_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = pdev->dev.of_node;
+ struct mux_control *mux;
+ struct mux_gpio *mux_gpio;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ mux = mux_control_alloc(sizeof(*mux_gpio));
+ if (!mux)
+ return -ENOMEM;
+ mux_gpio = mux->priv;
+ mux->dev.parent = dev;
+ mux->ops = &mux_gpio_ops;
+
+ platform_set_drvdata(pdev, mux);
+
+ mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW);
+ if (IS_ERR(mux_gpio->gpios)) {
+ if (PTR_ERR(mux_gpio->gpios) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get gpios\n");
+ mux_control_put(mux);
+ return PTR_ERR(mux_gpio->gpios);
+ }
+
+ ret = mux_control_register(mux);
+ if (ret < 0) {
+ dev_err(dev, "failed to register mux_control\n");
+ mux_control_put(mux);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int mux_gpio_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mux_control *mux = to_mux_control(dev);
+
+ mux_control_unregister(mux);
+ mux_control_put(mux);
+ return 0;
+}
+
+static struct platform_driver mux_gpio_driver = {
+ .driver = {
+ .name = "mux-gpio",
+ .of_match_table = of_match_ptr(mux_gpio_dt_ids),
+ },
+ .probe = mux_gpio_probe,
+ .remove = mux_gpio_remove,
+};
+module_platform_driver(mux_gpio_driver);
+
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org");
+MODULE_DESCRIPTION("GPIO-controlled multiplexer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mux.h b/include/linux/mux.h
new file mode 100644
index 000000000000..6513ffe749bb
--- /dev/null
+++ b/include/linux/mux.h
@@ -0,0 +1,54 @@
+/*
+ * mux.h - definitions for the multiplexer interface
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _LINUX_MUX_H
+#define _LINUX_MUX_H
+
+#include <linux/device.h>
+#include <linux/rwsem.h>
+
+struct mux_control;
+
+struct mux_control_ops {
+ int (*set)(struct mux_control *mux, int reg);
+};
+
+struct mux_control {
+ struct rw_semaphore lock; /* protects the state of the mux */
+
+ struct device dev;
+ int id;
+
+ int cache;
+ bool do_idle;
+ int idle_state;
+
+ const struct mux_control_ops *ops;
+
+ void *priv;
+};
+
+#define to_mux_control(x) container_of((x), struct mux_control, dev)
+
+struct mux_control *mux_control_alloc(size_t sizeof_priv);
+int mux_control_register(struct mux_control *mux);
+void mux_control_unregister(struct mux_control *mux);
+void mux_control_put(struct mux_control *mux);
+
+int mux_control_select(struct mux_control *mux, int reg);
+int mux_control_deselect(struct mux_control *mux);
+
+struct mux_control *mux_control_get(struct device *dev,
+ const char *mux_name);
+struct mux_control *devm_mux_control_get(struct device *dev,
+ const char *mux_name);
+void devm_mux_control_put(struct device *dev, struct mux_control *mux);
+
+#endif /* _LINUX_MUX_H */
--
2.1.4
^ permalink raw reply related
* [RFC PATCH 0/7] mux controller astraction and iio/i2c muxes
From: Peter Rosin @ 2016-11-16 23:48 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Arnd Bergmann, Greg Kroah-Hartman,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA
Hi!
This is work in progress, I'm asking for early feedback.
I have a piece of hardware that is using the same 3 GPIO pins
to control four 8-way muxes. Three of them control ADC lines
to an ADS1015 chip with an iio driver, and the last one
controls the SDA line of an i2c bus. We have some deployed
code to handle this, but you do not want to see it or ever
hear about it. I'm not sure why I even mention it. Anyway,
the situation has nagged me to no end for quite some time.
So, after first getting more intimate with the i2c muxing code
and later discovering the drivers/iio/inkern.c file and
writing a couple of drivers making use of it, I came up with
what I think is an acceptable solution; add a generic mux
controller driver that is shared between all instances, and
combine that with an iio mux driver and a new generic i2c mux
driver. The new i2c mux I called "simple" since it is only
hooking the i2c muxing and the new mux controller (much like
the alsa simple card driver does for ASoC).
My initial (private) version didn't add "mux" as a new bus,
but I couldn't get decent type-checking and nice devicetree
integration with that. It does however feel a bit rich to
add a new bus for something as small as mux controllers?
One thing that I would like to do, but don't see a solution
for, is to move the mux control code that is present in
various drivers in drivers/i2c/muxes to this new minimalistic
muxing subsystem, thus converting all present i2c muxes (but
perhaps not gates and arbitrators) to be i2c-mux-simple muxes.
I'm using an rwsem to lock a mux, but that isn't really a
perfect fit. Is there a better locking primitive that I don't
know about that fits better? I had a mutex at one point, but
that didn't allow any concurrent accesses at all. At least
the rwsem allows concurrent access as long as all users
agree on the mux state, but I suspect that the rwsem will
degrade to the mutex situation pretty quickly if there is
any contention.
Also, the "mux" name feels a bit ambitious, there are many muxes
in the world, and this tiny bit of code is probably not good
enough to be a nice fit for all...
This is all very fresh code and only lightly tested, but it
feels very promising! Now, go ahead and rip this to pieces...
Cheers,
Peter
Peter Rosin (7):
dt-bindings: document devicetree bindings for mux-gpio
misc: minimal mux subsystem and gpio-based mux controller
iio: inkern: api for manipulating ext_info of iio channels
dt-bindings: iio: iio-mux: document iio-mux bindings
iio: multiplexer: new iio category and iio-mux driver
dt-bindings: i2c: i2c-mux-simple: document i2c-mux-simple bindings
i2c: i2c-mux-simple: new driver
.../devicetree/bindings/i2c/i2c-mux-simple.txt | 91 ++++++
.../bindings/iio/multiplexer/iio-mux.txt | 59 ++++
.../devicetree/bindings/misc/mux-gpio.txt | 76 +++++
drivers/i2c/muxes/Kconfig | 12 +
drivers/i2c/muxes/Makefile | 1 +
drivers/i2c/muxes/i2c-mux-simple.c | 172 +++++++++++
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/inkern.c | 40 +++
drivers/iio/multiplexer/Kconfig | 17 ++
drivers/iio/multiplexer/Makefile | 6 +
drivers/iio/multiplexer/iio-mux.c | 313 +++++++++++++++++++++
drivers/misc/Kconfig | 6 +
drivers/misc/Makefile | 2 +
drivers/misc/mux-core.c | 285 +++++++++++++++++++
drivers/misc/mux-gpio.c | 105 +++++++
include/linux/iio/consumer.h | 5 +
include/linux/mux.h | 54 ++++
18 files changed, 1246 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
create mode 100644 Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
create mode 100644 Documentation/devicetree/bindings/misc/mux-gpio.txt
create mode 100644 drivers/i2c/muxes/i2c-mux-simple.c
create mode 100644 drivers/iio/multiplexer/Kconfig
create mode 100644 drivers/iio/multiplexer/Makefile
create mode 100644 drivers/iio/multiplexer/iio-mux.c
create mode 100644 drivers/misc/mux-core.c
create mode 100644 drivers/misc/mux-gpio.c
create mode 100644 include/linux/mux.h
--
2.1.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v4 07/14] ARM: dts: koelsch: use demuxer for I2C4
From: Simon Horman @ 2016-11-16 14:17 UTC (permalink / raw)
To: Geert Uytterhoeven; +Cc: Wolfram Sang, Linux-Renesas, Linux I2C, Ulrich Hecht
In-Reply-To: <CAMuHMdXsUA+OywpfuNwunuCSupczKSuWnAgs3Zdq7zMxrD6OWQ@mail.gmail.com>
On Wed, Nov 16, 2016 at 03:14:11PM +0100, Geert Uytterhoeven wrote:
> Hi Simon,
>
> On Wed, Nov 16, 2016 at 2:47 PM, Simon Horman <horms@verge.net.au> wrote:
> > On Wed, Nov 16, 2016 at 11:35:22AM +0100, Geert Uytterhoeven wrote:
> >> On Tue, Nov 15, 2016 at 6:44 PM, Simon Horman <horms@verge.net.au> wrote:
> >> >> i2c4 shares pins with vin0. Hence enabling the former breaks the latter:
> >> >>
> >> >> sh-pfc e6060000.pfc: pin GP_4_13 already requested by
> >> >> e6520000.i2c; cannot claim for e6ef0000.video
> >> >> sh-pfc e6060000.pfc: pin-141 (e6ef0000.video) status -22
> >> >> sh-pfc e6060000.pfc: could not request pin 141 (GP_4_13) from
> >> >> group vin0_data24 on device sh-pfc
> >> >> rcar-vin e6ef0000.video: Error applying setting, reverse things back
> >> >> rcar-vin: probe of e6ef0000.video failed with error -22
> >> >>
> >> >> There may be similar issues on other boards. Haven't checked yet.
> >> >
> >> > Thanks, I will drop this patch for now.
> >> >
> >> > I checked the boot logs of other boards with similar patches and didn't see
> >> > anything there.
> >>
> >> Do you have CONFIG_VIDEO_ADV7604=y?
> >
> > No, mainly because its not in shmobile_defconfig.
> >
> > I tried again with that option enabled and still didn't see
> > anything of interest in kernel log.
> >
> >> I expect the issue to be present on gose, too. So far I didn't see it
> >> there, yet.
> >> But the vin DTS of gose is different from koelsch, causing vin0 not to be
> >> initialized?
> >
> > It seems so.
>
> I managed to reproduce the issue on gose, by adding r8a7793 support to
> my koelsch .config.
> Apparently I was missing CONFIG_I2C_DEMUX_PINCTRL=y.
Ok, thanks. Curious that I didn't see it.
It looks like we should drop:
ARM: dts: gose: use demuxer for I2C4
^ permalink raw reply
* Re: [PATCH v4 07/14] ARM: dts: koelsch: use demuxer for I2C4
From: Geert Uytterhoeven @ 2016-11-16 14:14 UTC (permalink / raw)
To: Simon Horman; +Cc: Wolfram Sang, Linux-Renesas, Linux I2C, Ulrich Hecht
In-Reply-To: <20161116134736.GA20704@verge.net.au>
Hi Simon,
On Wed, Nov 16, 2016 at 2:47 PM, Simon Horman <horms@verge.net.au> wrote:
> On Wed, Nov 16, 2016 at 11:35:22AM +0100, Geert Uytterhoeven wrote:
>> On Tue, Nov 15, 2016 at 6:44 PM, Simon Horman <horms@verge.net.au> wrote:
>> >> i2c4 shares pins with vin0. Hence enabling the former breaks the latter:
>> >>
>> >> sh-pfc e6060000.pfc: pin GP_4_13 already requested by
>> >> e6520000.i2c; cannot claim for e6ef0000.video
>> >> sh-pfc e6060000.pfc: pin-141 (e6ef0000.video) status -22
>> >> sh-pfc e6060000.pfc: could not request pin 141 (GP_4_13) from
>> >> group vin0_data24 on device sh-pfc
>> >> rcar-vin e6ef0000.video: Error applying setting, reverse things back
>> >> rcar-vin: probe of e6ef0000.video failed with error -22
>> >>
>> >> There may be similar issues on other boards. Haven't checked yet.
>> >
>> > Thanks, I will drop this patch for now.
>> >
>> > I checked the boot logs of other boards with similar patches and didn't see
>> > anything there.
>>
>> Do you have CONFIG_VIDEO_ADV7604=y?
>
> No, mainly because its not in shmobile_defconfig.
>
> I tried again with that option enabled and still didn't see
> anything of interest in kernel log.
>
>> I expect the issue to be present on gose, too. So far I didn't see it
>> there, yet.
>> But the vin DTS of gose is different from koelsch, causing vin0 not to be
>> initialized?
>
> It seems so.
I managed to reproduce the issue on gose, by adding r8a7793 support to
my koelsch .config.
Apparently I was missing CONFIG_I2C_DEMUX_PINCTRL=y.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH v4 07/14] ARM: dts: koelsch: use demuxer for I2C4
From: Geert Uytterhoeven @ 2016-11-16 13:48 UTC (permalink / raw)
To: Wolfram Sang; +Cc: Linux-Renesas, Simon Horman, Linux I2C, Simon Horman
In-Reply-To: <CAMuHMdXzqakz6RYXFMOe5ps0SPsxDWHbOV024ts_E_qufVOutQ@mail.gmail.com>
On Tue, Nov 15, 2016 at 4:45 PM, Geert Uytterhoeven
<geert@linux-m68k.org> wrote:
> i2c4 shares pins with vin0. Hence enabling the former breaks the latter:
>
> sh-pfc e6060000.pfc: pin GP_4_13 already requested by
> e6520000.i2c; cannot claim for e6ef0000.video
> sh-pfc e6060000.pfc: pin-141 (e6ef0000.video) status -22
> sh-pfc e6060000.pfc: could not request pin 141 (GP_4_13) from
> group vin0_data24 on device sh-pfc
> rcar-vin e6ef0000.video: Error applying setting, reverse things back
> rcar-vin: probe of e6ef0000.video failed with error -22
>
> There may be similar issues on other boards. Haven't checked yet.
I inspected the DTSes for Koelsch, Lager, Gose, and Alt, looking for
conflicting configurations of pins:
- Lager:
- i2c1 and iic1 share GP1_16 and GP1_17, but that is handled through
i2c-demux-pinctrl
- usb0_ovc_vbus and usb0 share GP5_19, but that is probably OK, as these
groups are configured for the same function?
- Koelsch: i2c4 and and vin0_data24 share pins GP4_13 and GP4_14
(vin0_data8 would be OK)
- Gose: Same issue as Koelsch.
- Alt: No conflicts.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH v4 07/14] ARM: dts: koelsch: use demuxer for I2C4
From: Simon Horman @ 2016-11-16 13:47 UTC (permalink / raw)
To: Geert Uytterhoeven; +Cc: Wolfram Sang, Linux-Renesas, Linux I2C, Ulrich Hecht
In-Reply-To: <CAMuHMdVVqhZgTjaFiGgviCRD0X0DkQPZjpnta2FOhFmV7M+CvQ@mail.gmail.com>
On Wed, Nov 16, 2016 at 11:35:22AM +0100, Geert Uytterhoeven wrote:
> Hi Simon,
>
> On Tue, Nov 15, 2016 at 6:44 PM, Simon Horman <horms@verge.net.au> wrote:
> >> i2c4 shares pins with vin0. Hence enabling the former breaks the latter:
> >>
> >> sh-pfc e6060000.pfc: pin GP_4_13 already requested by
> >> e6520000.i2c; cannot claim for e6ef0000.video
> >> sh-pfc e6060000.pfc: pin-141 (e6ef0000.video) status -22
> >> sh-pfc e6060000.pfc: could not request pin 141 (GP_4_13) from
> >> group vin0_data24 on device sh-pfc
> >> rcar-vin e6ef0000.video: Error applying setting, reverse things back
> >> rcar-vin: probe of e6ef0000.video failed with error -22
> >>
> >> There may be similar issues on other boards. Haven't checked yet.
> >
> > Thanks, I will drop this patch for now.
> >
> > I checked the boot logs of other boards with similar patches and didn't see
> > anything there.
>
> Do you have CONFIG_VIDEO_ADV7604=y?
No, mainly because its not in shmobile_defconfig.
I tried again with that option enabled and still didn't see
anything of interest in kernel log.
> I expect the issue to be present on gose, too. So far I didn't see it
> there, yet.
> But the vin DTS of gose is different from koelsch, causing vin0 not to be
> initialized?
It seems so.
^ permalink raw reply
* Re: [PATCH v2 0/3] i2c: octeon: thunder: Fix i2c not working on Octeon
From: Paul Burton @ 2016-11-16 10:38 UTC (permalink / raw)
To: Jan Glauber
Cc: Steven J. Hill, Wolfram Sang, linux-i2c, linux-mips, David Daney
In-Reply-To: <20161115130314.GC2772@hardcore>
[-- Attachment #1: Type: text/plain, Size: 1344 bytes --]
Hi Jan,
On Tuesday, 15 November 2016 14:03:15 GMT Jan Glauber wrote:
> On Mon, Nov 14, 2016 at 01:53:40PM -0600, Steven J. Hill wrote:
> > On 11/14/2016 12:50 PM, Jan Glauber wrote:
> > > Since time is running out for 4.9 (or might have already if you're not
> > > going to send another pull request) I'm going for the safe option
> > > to fix the Octeon i2c problems, which is:
> > >
> > > 1. Reverting the readq_poll_timeout patch since it is broken
> > > 2. Apply Patch #2 from Paul
> > > 3. Add a small fix for the recovery that makes Paul's patch
> > >
> > > work on ThunderX
> > >
> > > I'll try to come up with a better solution for 4.10. My plan is to get
> > > rid
> > > of the polling-around-interrupt thing completely, but for that we need
> > > more
> > > time to make it work on Octeon.
> > >
> > > Please consider for 4.9.
> >
> > Hey Jan.
> >
> > This does not work on Octeon 71xx platforms. I will look at it more
> > closely tomorrow.
>
> Paul, can you confirm this? It doesn't make sense for me, since patches #1
> and #3 are unlikely to break anything... And patch #2 worked for you.
For me v4.9-rc5 plus these 3 patches boots fine on a Rhino Labs UTM8 system
which previously hung whilst probing the I2C driver & devices. Feel free to
add:
Tested-by: Paul Burton <paul.burton@imgtec.com>
Thanks,
Paul
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 801 bytes --]
^ permalink raw reply
* Re: [PATCH v4 07/14] ARM: dts: koelsch: use demuxer for I2C4
From: Geert Uytterhoeven @ 2016-11-16 10:35 UTC (permalink / raw)
To: Simon Horman; +Cc: Wolfram Sang, Linux-Renesas, Linux I2C, Ulrich Hecht
In-Reply-To: <20161115174450.GA21715@verge.net.au>
Hi Simon,
On Tue, Nov 15, 2016 at 6:44 PM, Simon Horman <horms@verge.net.au> wrote:
>> i2c4 shares pins with vin0. Hence enabling the former breaks the latter:
>>
>> sh-pfc e6060000.pfc: pin GP_4_13 already requested by
>> e6520000.i2c; cannot claim for e6ef0000.video
>> sh-pfc e6060000.pfc: pin-141 (e6ef0000.video) status -22
>> sh-pfc e6060000.pfc: could not request pin 141 (GP_4_13) from
>> group vin0_data24 on device sh-pfc
>> rcar-vin e6ef0000.video: Error applying setting, reverse things back
>> rcar-vin: probe of e6ef0000.video failed with error -22
>>
>> There may be similar issues on other boards. Haven't checked yet.
>
> Thanks, I will drop this patch for now.
>
> I checked the boot logs of other boards with similar patches and didn't see
> anything there.
Do you have CONFIG_VIDEO_ADV7604=y?
I expect the issue to be present on gose, too. So far I didn't see it
there, yet.
But the vin DTS of gose is different from koelsch, causing vin0 not to be
initialized?
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: EMail problems :(
From: Wolfram Sang @ 2016-11-16 8:15 UTC (permalink / raw)
To: linux-i2c
In-Reply-To: <20161116081212.GD1797@katana>
[-- Attachment #1: Type: text/plain, Size: 139 bytes --]
> my main mail service (the-dreams.de) seems to be defunct currently.
No need to resend patches, though. They are tracked in patchwork.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* EMail problems :(
From: Wolfram Sang @ 2016-11-16 8:12 UTC (permalink / raw)
To: linux-i2c, linux-renesas-soc
[-- Attachment #1: Type: text/plain, Size: 185 bytes --]
Hi all,
my main mail service (the-dreams.de) seems to be defunct currently.
Until this is resolved, I can be reached via this email address.
Sorry for the inconvenience,
Wolfram
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* Re: [PATCH resend v4] i2c: hibvt: add Hisilicon BVT I2C controller driver
From: Jiancheng Xue @ 2016-11-16 8:00 UTC (permalink / raw)
To: wsa
Cc: Pan Wen, robh+dt, mark.rutland, linux-i2c, devicetree,
linux-kernel, howell.yang, jalen.hsu, lvkuanliang, suwenping,
raojun, kevin.lixu
In-Reply-To: <20161021081709.7307-1-wenpan@hisilicon.com>
Hi Wolfram,
On 2016/10/21 16:17, Pan Wen wrote:
> add Hisilicon BVT I2C controller driver support.
>
> Signed-off-by: Pan Wen <wenpan@hisilicon.com>
> Acked-by: Rob Herring <robh@kernel.org>
> ---
> change log
> v4:
> Modify the default frequency to 100KHz.
> v3:
> Add a SoC specific compatible string.
> v2:
> 1)Fixed a compile error.
> 2)Dropped the clock-names property.
> .../devicetree/bindings/i2c/i2c-hibvt.txt | 24 +
> drivers/i2c/busses/Kconfig | 10 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-hibvt.c | 737 +++++++++++++++++++++
> 4 files changed, 772 insertions(+)
Could you give us some comments on this patch?
If there are some issues about it, please let us know.
Thank you very much!
Best Regards,
Jiancheng
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-hibvt.txt
> create mode 100644 drivers/i2c/busses/i2c-hibvt.c
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-hibvt.txt b/Documentation/devicetree/bindings/i2c/i2c-hibvt.txt
> new file mode 100644
> index 0000000..db3d2e2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-hibvt.txt
> @@ -0,0 +1,24 @@
> +Hisilicon BVT I2C master controller
> +
> +Required properties:
> +- compatible: should be "hisilicon,hibvt-i2c" and one of the following:
> + "hisilicon,hi3516cv300-i2c"
> +- reg: physical base address of the controller and length of memory mapped.
> + region.
> +- interrupts: interrupt number to the cpu.
> +- clocks: phandles to input clocks.
> +
> +Optional properties:
> +- clock-frequency: Desired I2C bus frequency in Hz, otherwise defaults to 100000.
> +
> +Other properties:
> +see Documentation/devicetree/bindings/i2c/i2c.txt.
> +
> +Examples:
> +i2c_bus0: i2c@12110000 {
> + compatible = "hisilicon,hi3516cv300-i2c", "hisilicon,hibvt-i2c";
> + reg = <0x12110000 0x100>;
> + interrupts = <20>;
> + clocks = <&crg_ctrl HI3516CV300_APB_CLK>;
> + clock-frequency = <100000>;
> +};
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 5c3993b..fc1b679 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -555,6 +555,16 @@ config I2C_GPIO
> This is a very simple bitbanging I2C driver utilizing the
> arch-neutral GPIO API to control the SCL and SDA lines.
>
> +config I2C_HIBVT
> + tristate "Hisilicon BVT I2C Controller"
> + depends on ARCH_HISI
> + help
> + Say Y here to include support for Hisilicon BVT I2C controller in the
> + Hisilicon BVT SoCs.
> +
> + This driver can also be built as a module. If so, the module
> + will be called i2c-hibvt.
> +
> config I2C_HIGHLANDER
> tristate "Highlander FPGA SMBus interface"
> depends on SH_HIGHLANDER
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 37f2819..42ef2e0 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -51,6 +51,7 @@ obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
> obj-$(CONFIG_I2C_EMEV2) += i2c-emev2.o
> obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
> obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
> +obj-$(CONFIG_I2C_HIBVT) += i2c-hibvt.o
> obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
> obj-$(CONFIG_I2C_HIX5HD2) += i2c-hix5hd2.o
> obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
> diff --git a/drivers/i2c/busses/i2c-hibvt.c b/drivers/i2c/busses/i2c-hibvt.c
> new file mode 100644
> index 0000000..e8af63e
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-hibvt.c
> @@ -0,0 +1,737 @@
> +/*
> + * Hisilicon BVT I2C Controller Driver
> + *
> + * Copyright (c) 2016 HiSilicon Technologies Co., Ltd.
> + *
> + * Authors: wenpan@hisilicon.com
> + *
> + * 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.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +/*
> + * I2C Registers offsets
> + */
> +#define HIBVT_I2C_GLB 0x0
> +#define HIBVT_I2C_SCL_H 0x4
> +#define HIBVT_I2C_SCL_L 0x8
> +#define HIBVT_I2C_DATA1 0x10
> +#define HIBVT_I2C_TXF 0x20
> +#define HIBVT_I2C_RXF 0x24
> +#define HIBVT_I2C_CMD_BASE 0x30
> +#define HIBVT_I2C_LOOP1 0xb0
> +#define HIBVT_I2C_DST1 0xb4
> +#define HIBVT_I2C_TX_WATER 0xc8
> +#define HIBVT_I2C_RX_WATER 0xcc
> +#define HIBVT_I2C_CTRL1 0xd0
> +#define HIBVT_I2C_STAT 0xd8
> +#define HIBVT_I2C_INTR_RAW 0xe0
> +#define HIBVT_I2C_INTR_EN 0xe4
> +#define HIBVT_I2C_INTR_STAT 0xe8
> +
> +/*
> + * I2C Global Config Register -- HIBVT_I2C_GLB
> + */
> +#define GLB_EN_MASK BIT(0)
> +#define GLB_SDA_HOLD_MASK GENMASK(23, 8)
> +#define GLB_SDA_HOLD_SHIFT (8)
> +
> +/*
> + * I2C Timing CMD Register -- HIBVT_I2C_CMD_BASE + n * 4 (n = 0, 1, 2, ... 31)
> + */
> +#define CMD_EXIT 0x0
> +#define CMD_TX_S 0x1
> +#define CMD_TX_D1_2 0x4
> +#define CMD_TX_D1_1 0x5
> +#define CMD_TX_FIFO 0x9
> +#define CMD_RX_FIFO 0x12
> +#define CMD_RX_ACK 0x13
> +#define CMD_IGN_ACK 0x15
> +#define CMD_TX_ACK 0x16
> +#define CMD_TX_NACK 0x17
> +#define CMD_JMP1 0x18
> +#define CMD_UP_TXF 0x1d
> +#define CMD_TX_RS 0x1e
> +#define CMD_TX_P 0x1f
> +
> +/*
> + * I2C Control Register 1 -- HIBVT_I2C_CTRL1
> + */
> +#define CTRL1_CMD_START_MASK BIT(0)
> +
> +/*
> + * I2C Status Register -- HIBVT_I2C_STAT
> + */
> +#define STAT_RXF_NOE_MASK BIT(16) /* RX FIFO not empty flag */
> +#define STAT_TXF_NOF_MASK BIT(19) /* TX FIFO not full flag */
> +
> +
> +/*
> + * I2C Interrupt status and mask Register --
> + * HIBVT_I2C_INTR_RAW, HIBVT_I2C_STAT, HIBVT_I2C_INTR_STAT
> + */
> +#define INTR_ABORT_MASK (BIT(0) | BIT(11))
> +#define INTR_RX_MASK BIT(2)
> +#define INTR_TX_MASK BIT(4)
> +#define INTR_CMD_DONE_MASK BIT(12)
> +#define INTR_USE_MASK (INTR_ABORT_MASK \
> + |INTR_RX_MASK \
> + | INTR_TX_MASK \
> + | INTR_CMD_DONE_MASK)
> +#define INTR_ALL_MASK GENMASK(31, 0)
> +
> +#define I2C_DEFAULT_FREQUENCY 100000
> +#define I2C_TXF_DEPTH 64
> +#define I2C_RXF_DEPTH 64
> +#define I2C_TXF_WATER 32
> +#define I2C_RXF_WATER 32
> +#define I2C_WAIT_TIMEOUT 0x10000
> +#define I2C_IRQ_TIMEOUT (msecs_to_jiffies(1000))
> +
> +
> +struct hibvt_i2c_dev {
> + struct device *dev;
> + struct i2c_adapter adap;
> + void __iomem *base;
> + struct clk *clk;
> + int irq;
> +
> + unsigned int freq;
> + struct i2c_msg *msg;
> + unsigned int msg_num;
> + unsigned int msg_idx;
> + unsigned int msg_buf_ptr;
> + struct completion msg_complete;
> +
> + spinlock_t lock;
> + int status;
> +};
> +
> +static inline void hibvt_i2c_disable(struct hibvt_i2c_dev *i2c)
> +{
> + unsigned int val;
> +
> + val = readl(i2c->base + HIBVT_I2C_GLB);
> + val &= ~GLB_EN_MASK;
> + writel(val, i2c->base + HIBVT_I2C_GLB);
> +}
> +
> +static inline void hibvt_i2c_enable(struct hibvt_i2c_dev *i2c)
> +{
> + unsigned int val;
> +
> + val = readl(i2c->base + HIBVT_I2C_GLB);
> + val |= GLB_EN_MASK;
> + writel(val, i2c->base + HIBVT_I2C_GLB);
> +}
> +
> +static inline void hibvt_i2c_cfg_irq(struct hibvt_i2c_dev *i2c,
> + unsigned int flag)
> +{
> + writel(flag, i2c->base + HIBVT_I2C_INTR_EN);
> +}
> +
> +static inline void hibvt_i2c_disable_irq(struct hibvt_i2c_dev *i2c,
> + unsigned int flag)
> +{
> + unsigned int val;
> +
> + val = readl(i2c->base + HIBVT_I2C_INTR_EN);
> + val &= ~flag;
> + writel(val, i2c->base + HIBVT_I2C_INTR_EN);
> +}
> +
> +static inline unsigned int hibvt_i2c_clr_irq(struct hibvt_i2c_dev *i2c)
> +{
> + unsigned int val;
> +
> + val = readl(i2c->base + HIBVT_I2C_INTR_STAT);
> + writel(INTR_ALL_MASK, i2c->base + HIBVT_I2C_INTR_RAW);
> +
> + return val;
> +}
> +
> +static inline void hibvt_i2c_cmdreg_set(struct hibvt_i2c_dev *i2c,
> + unsigned int cmd, unsigned int *offset)
> +{
> + dev_dbg(i2c->dev, "hii2c reg: offset=0x%x, cmd=0x%x...\n",
> + *offset * 4, cmd);
> + writel(cmd, i2c->base + HIBVT_I2C_CMD_BASE + *offset * 4);
> + (*offset)++;
> +}
> +
> +/*
> + * config i2c slave addr
> + */
> +static inline void hibvt_i2c_set_addr(struct hibvt_i2c_dev *i2c)
> +{
> + struct i2c_msg *msg = i2c->msg;
> + u16 addr;
> +
> + if (msg->flags & I2C_M_TEN) {
> + /* First byte is 11110XX0 where XX is upper 2 bits */
> + addr = ((msg->addr & 0x300) << 1) | 0xf000;
> + if (msg->flags & I2C_M_RD)
> + addr |= 1 << 8;
> +
> + /* Second byte is the remaining 8 bits */
> + addr |= msg->addr & 0xff;
> + } else {
> + addr = (msg->addr & 0x7f) << 1;
> + if (msg->flags & I2C_M_RD)
> + addr |= 1;
> + }
> +
> + writel(addr, i2c->base + HIBVT_I2C_DATA1);
> +}
> +
> +/*
> + * Start command sequence
> + */
> +static inline void hibvt_i2c_start_cmd(struct hibvt_i2c_dev *i2c)
> +{
> + unsigned int val;
> +
> + val = readl(i2c->base + HIBVT_I2C_CTRL1);
> + val |= CTRL1_CMD_START_MASK;
> + writel(val, i2c->base + HIBVT_I2C_CTRL1);
> +}
> +
> +static int hibvt_i2c_wait_rx_noempty(struct hibvt_i2c_dev *i2c)
> +{
> + unsigned int time_cnt = 0;
> + unsigned int val;
> +
> + do {
> + val = readl(i2c->base + HIBVT_I2C_STAT);
> + if (val & STAT_RXF_NOE_MASK)
> + return 0;
> +
> + udelay(50);
> + } while (time_cnt++ < I2C_WAIT_TIMEOUT);
> +
> + dev_err(i2c->dev, "wait rx no empty timeout, RIS: 0x%x, SR: 0x%x\n",
> + readl(i2c->base + HIBVT_I2C_INTR_RAW), val);
> + return -EIO;
> +}
> +
> +static int hibvt_i2c_wait_tx_nofull(struct hibvt_i2c_dev *i2c)
> +{
> + unsigned int time_cnt = 0;
> + unsigned int val;
> +
> + do {
> + val = readl(i2c->base + HIBVT_I2C_STAT);
> + if (val & STAT_TXF_NOF_MASK)
> + return 0;
> +
> + udelay(50);
> + } while (time_cnt++ < I2C_WAIT_TIMEOUT);
> +
> + dev_err(i2c->dev, "wait rx no empty timeout, RIS: 0x%x, SR: 0x%x\n",
> + readl(i2c->base + HIBVT_I2C_INTR_RAW), val);
> + return -EIO;
> +}
> +
> +static int hibvt_i2c_wait_idle(struct hibvt_i2c_dev *i2c)
> +{
> + unsigned int time_cnt = 0;
> + unsigned int val;
> +
> + do {
> + val = readl(i2c->base + HIBVT_I2C_INTR_RAW);
> + if (val & (INTR_ABORT_MASK)) {
> + dev_err(i2c->dev, "wait idle abort!, RIS: 0x%x\n",
> + val);
> + return -EIO;
> + }
> +
> + if (val & INTR_CMD_DONE_MASK)
> + return 0;
> +
> + udelay(50);
> + } while (time_cnt++ < I2C_WAIT_TIMEOUT);
> +
> + dev_err(i2c->dev, "wait idle timeout, RIS: 0x%x, SR: 0x%x\n",
> + val, readl(i2c->base + HIBVT_I2C_STAT));
> +
> + return -EIO;
> +}
> +
> +static void hibvt_i2c_set_freq(struct hibvt_i2c_dev *i2c)
> +{
> + unsigned int max_freq, freq;
> + unsigned int clk_rate;
> + unsigned int val, sda_hold;
> +
> + freq = i2c->freq;
> + clk_rate = clk_get_rate(i2c->clk);
> + max_freq = clk_rate >> 1;
> +
> + if (freq > max_freq) {
> + i2c->freq = max_freq;
> + freq = i2c->freq;
> + }
> +
> + if (freq <= 100000) {
> + val = clk_rate / (freq * 2) - 1;
> + writel(val, i2c->base + HIBVT_I2C_SCL_H);
> + writel(val, i2c->base + HIBVT_I2C_SCL_L);
> + } else {
> + val = (clk_rate * 36) / (freq * 100);
> + writel(val, i2c->base + HIBVT_I2C_SCL_H);
> + val = (clk_rate * 64) / (freq * 100);
> + writel(val, i2c->base + HIBVT_I2C_SCL_L);
> + }
> +
> + sda_hold = val * 3 / 10;
> + sda_hold = (sda_hold << GLB_SDA_HOLD_SHIFT) & GLB_SDA_HOLD_MASK;
> + val = readl(i2c->base + HIBVT_I2C_GLB);
> + val &= ~GLB_SDA_HOLD_MASK;
> + val |= sda_hold;
> + writel(val, i2c->base + HIBVT_I2C_GLB);
> +}
> +
> +/*
> + * set i2c controller TX and RX FIFO water
> + */
> +static inline void hibvt_i2c_set_water(struct hibvt_i2c_dev *i2c)
> +{
> + writel(I2C_TXF_WATER, i2c->base + HIBVT_I2C_TX_WATER);
> + writel(I2C_RXF_WATER, i2c->base + HIBVT_I2C_RX_WATER);
> +}
> +
> +/*
> + * initialise the controller, set i2c bus interface freq
> + */
> +static void hibvt_i2c_hw_init(struct hibvt_i2c_dev *i2c)
> +{
> + hibvt_i2c_disable(i2c);
> + hibvt_i2c_disable_irq(i2c, INTR_ALL_MASK);
> + hibvt_i2c_set_freq(i2c);
> + hibvt_i2c_set_water(i2c);
> +}
> +
> +/*
> + * hibvt_i2c_cfg_cmd - config i2c controller command sequence
> + *
> + * After all the timing command is configured,
> + * and then start the command, you can i2c communication,
> + * and then only need to read and write i2c fifo.
> + */
> +static void hibvt_i2c_cfg_cmd(struct hibvt_i2c_dev *i2c)
> +{
> + struct i2c_msg *msg = i2c->msg;
> + int offset = 0;
> +
> + if (i2c->msg_idx == 0)
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_S, &offset);
> + else
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_RS, &offset);
> +
> + if (msg->flags & I2C_M_TEN) {
> + if (i2c->msg_idx == 0) {
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_D1_2, &offset);
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_D1_1, &offset);
> + } else {
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_D1_2, &offset);
> + }
> + } else {
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_D1_1, &offset);
> + }
> +
> + if (msg->flags & I2C_M_IGNORE_NAK)
> + hibvt_i2c_cmdreg_set(i2c, CMD_IGN_ACK, &offset);
> + else
> + hibvt_i2c_cmdreg_set(i2c, CMD_RX_ACK, &offset);
> +
> + if (msg->flags & I2C_M_RD) {
> + if (msg->len >= 2) {
> + writel(offset, i2c->base + HIBVT_I2C_DST1);
> + writel(msg->len - 2, i2c->base + HIBVT_I2C_LOOP1);
> + hibvt_i2c_cmdreg_set(i2c, CMD_RX_FIFO, &offset);
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_ACK, &offset);
> + hibvt_i2c_cmdreg_set(i2c, CMD_JMP1, &offset);
> + }
> + hibvt_i2c_cmdreg_set(i2c, CMD_RX_FIFO, &offset);
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_NACK, &offset);
> + } else {
> + writel(offset, i2c->base + HIBVT_I2C_DST1);
> + writel(msg->len - 1, i2c->base + HIBVT_I2C_LOOP1);
> + hibvt_i2c_cmdreg_set(i2c, CMD_UP_TXF, &offset);
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_FIFO, &offset);
> +
> + if (msg->flags & I2C_M_IGNORE_NAK)
> + hibvt_i2c_cmdreg_set(i2c, CMD_IGN_ACK, &offset);
> + else
> + hibvt_i2c_cmdreg_set(i2c, CMD_RX_ACK, &offset);
> +
> + hibvt_i2c_cmdreg_set(i2c, CMD_JMP1, &offset);
> + }
> +
> + if ((i2c->msg_idx == (i2c->msg_num - 1)) || (msg->flags & I2C_M_STOP)) {
> + dev_dbg(i2c->dev, "run to %s %d...TX STOP\n",
> + __func__, __LINE__);
> + hibvt_i2c_cmdreg_set(i2c, CMD_TX_P, &offset);
> + }
> +
> + hibvt_i2c_cmdreg_set(i2c, CMD_EXIT, &offset);
> +}
> +
> +static int hibvt_i2c_polling_xfer_one_msg(struct hibvt_i2c_dev *i2c)
> +{
> + int status;
> + unsigned int val;
> + struct i2c_msg *msg = i2c->msg;
> +
> + dev_dbg(i2c->dev, "[%s,%d]msg->flags=0x%x, len=0x%x\n",
> + __func__, __LINE__, msg->flags, msg->len);
> +
> + hibvt_i2c_enable(i2c);
> + hibvt_i2c_clr_irq(i2c);
> + hibvt_i2c_set_addr(i2c);
> + hibvt_i2c_cfg_cmd(i2c);
> + hibvt_i2c_start_cmd(i2c);
> +
> + i2c->msg_buf_ptr = 0;
> +
> + if (msg->flags & I2C_M_RD) {
> + while (i2c->msg_buf_ptr < msg->len) {
> + status = hibvt_i2c_wait_rx_noempty(i2c);
> + if (status)
> + goto end;
> +
> + val = readl(i2c->base + HIBVT_I2C_RXF);
> + msg->buf[i2c->msg_buf_ptr] = val;
> + i2c->msg_buf_ptr++;
> +
> + }
> + } else {
> + while (i2c->msg_buf_ptr < msg->len) {
> + status = hibvt_i2c_wait_tx_nofull(i2c);
> + if (status)
> + goto end;
> +
> + val = msg->buf[i2c->msg_buf_ptr];
> + writel(val, i2c->base + HIBVT_I2C_TXF);
> + i2c->msg_buf_ptr++;
> + }
> + }
> +
> + status = hibvt_i2c_wait_idle(i2c);
> +end:
> + hibvt_i2c_disable(i2c);
> +
> + return status;
> +}
> +
> +static irqreturn_t hibvt_i2c_isr(int irq, void *dev_id)
> +{
> + struct hibvt_i2c_dev *i2c = dev_id;
> + unsigned int irq_status;
> + struct i2c_msg *msg = i2c->msg;
> +
> + spin_lock(&i2c->lock);
> +
> + irq_status = hibvt_i2c_clr_irq(i2c);
> + dev_dbg(i2c->dev, "%s RIS: 0x%x\n", __func__, irq_status);
> +
> + if (!irq_status) {
> + dev_dbg(i2c->dev, "no irq\n");
> + goto end;
> + }
> +
> + if (irq_status & INTR_ABORT_MASK) {
> + dev_err(i2c->dev, "irq handle abort, RIS: 0x%x\n",
> + irq_status);
> + i2c->status = -EIO;
> + hibvt_i2c_disable_irq(i2c, INTR_ALL_MASK);
> +
> + complete(&i2c->msg_complete);
> + goto end;
> + }
> +
> + if (msg->flags & I2C_M_RD) {
> + while ((readl(i2c->base + HIBVT_I2C_STAT) & STAT_RXF_NOE_MASK)
> + && (i2c->msg_buf_ptr < msg->len)) {
> + msg->buf[i2c->msg_buf_ptr] =
> + readl(i2c->base + HIBVT_I2C_RXF);
> + i2c->msg_buf_ptr++;
> + }
> + } else {
> + while ((readl(i2c->base + HIBVT_I2C_STAT) & STAT_TXF_NOF_MASK)
> + && (i2c->msg_buf_ptr < msg->len)) {
> + writel(msg->buf[i2c->msg_buf_ptr],
> + i2c->base + HIBVT_I2C_TXF);
> + i2c->msg_buf_ptr++;
> + }
> + }
> +
> + if (i2c->msg_buf_ptr >= msg->len)
> + hibvt_i2c_disable_irq(i2c, INTR_TX_MASK | INTR_RX_MASK);
> +
> + if (irq_status & INTR_CMD_DONE_MASK) {
> + dev_dbg(i2c->dev, "cmd done\n");
> + i2c->status = 0;
> + hibvt_i2c_disable_irq(i2c, INTR_ALL_MASK);
> +
> + complete(&i2c->msg_complete);
> + }
> +
> +end:
> + spin_unlock(&i2c->lock);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int hibvt_i2c_interrupt_xfer_one_msg(struct hibvt_i2c_dev *i2c)
> +{
> + int status;
> + struct i2c_msg *msg = i2c->msg;
> + unsigned long timeout;
> + unsigned long flags;
> +
> + dev_dbg(i2c->dev, "[%s,%d]msg->flags=0x%x, len=0x%x\n",
> + __func__, __LINE__, msg->flags, msg->len);
> +
> + reinit_completion(&i2c->msg_complete);
> + i2c->msg_buf_ptr = 0;
> + i2c->status = -EIO;
> +
> + spin_lock_irqsave(&i2c->lock, flags);
> + hibvt_i2c_enable(i2c);
> + hibvt_i2c_clr_irq(i2c);
> + if (msg->flags & I2C_M_RD)
> + hibvt_i2c_cfg_irq(i2c, INTR_USE_MASK & ~INTR_TX_MASK);
> + else
> + hibvt_i2c_cfg_irq(i2c, INTR_USE_MASK & ~INTR_RX_MASK);
> +
> + hibvt_i2c_set_addr(i2c);
> + hibvt_i2c_cfg_cmd(i2c);
> + hibvt_i2c_start_cmd(i2c);
> + spin_unlock_irqrestore(&i2c->lock, flags);
> +
> + timeout = wait_for_completion_timeout(&i2c->msg_complete,
> + I2C_IRQ_TIMEOUT);
> +
> + if (timeout == 0) {
> + hibvt_i2c_disable_irq(i2c, INTR_ALL_MASK);
> + status = -EIO;
> + dev_err(i2c->dev, "%s timeout\n",
> + msg->flags & I2C_M_RD ? "rx" : "tx");
> + } else {
> + status = i2c->status;
> + }
> +
> + hibvt_i2c_disable(i2c);
> +
> + return status;
> +}
> +
> +/*
> + * Master transfer function
> + */
> +static int hibvt_i2c_xfer(struct i2c_adapter *adap,
> + struct i2c_msg *msgs, int num)
> +{
> + struct hibvt_i2c_dev *i2c = i2c_get_adapdata(adap);
> + int status;
> +
> + if (!msgs) {
> + dev_err(i2c->dev, "msgs == NULL\n");
> + return -EIO;
> + }
> +
> + i2c->msg = msgs;
> + i2c->msg_num = num;
> + i2c->msg_idx = 0;
> +
> + if (i2c->irq >= 0) {
> + while (i2c->msg_idx < i2c->msg_num) {
> + status = hibvt_i2c_interrupt_xfer_one_msg(i2c);
> + if (status)
> + break;
> +
> + i2c->msg++;
> + i2c->msg_idx++;
> + }
> + } else {
> + while (i2c->msg_idx < i2c->msg_num) {
> + status = hibvt_i2c_polling_xfer_one_msg(i2c);
> + if (status)
> + break;
> +
> + i2c->msg++;
> + i2c->msg_idx++;
> + }
> + }
> +
> + if (!status || i2c->msg_idx > 0)
> + status = i2c->msg_idx;
> +
> + return status;
> +}
> +
> +static u32 hibvt_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR
> + | I2C_FUNC_PROTOCOL_MANGLING;
> +}
> +
> +static const struct i2c_algorithm hibvt_i2c_algo = {
> + .master_xfer = hibvt_i2c_xfer,
> + .functionality = hibvt_i2c_func,
> +};
> +
> +static int hibvt_i2c_probe(struct platform_device *pdev)
> +{
> + int status;
> + struct hibvt_i2c_dev *i2c;
> + struct i2c_adapter *adap;
> + struct resource *res;
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
> + if (!i2c)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, i2c);
> + i2c->dev = &pdev->dev;
> + spin_lock_init(&i2c->lock);
> + init_completion(&i2c->msg_complete);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + i2c->base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(i2c->base)) {
> + dev_err(i2c->dev, "cannot ioremap resource\n");
> + return -ENOMEM;
> + }
> +
> + i2c->clk = devm_clk_get(&pdev->dev, NULL);
> + if (IS_ERR(i2c->clk)) {
> + dev_err(i2c->dev, "cannot get clock\n");
> + return -ENOENT;
> + }
> + clk_prepare_enable(i2c->clk);
> +
> + if (of_property_read_u32(pdev->dev.of_node, "clock-frequency",
> + &i2c->freq)) {
> + dev_warn(i2c->dev, "setting default clock-frequency@%dHz\n",
> + I2C_DEFAULT_FREQUENCY);
> + i2c->freq = I2C_DEFAULT_FREQUENCY;
> + }
> +
> + /* i2c controller initialization, disable interrupt */
> + hibvt_i2c_hw_init(i2c);
> +
> + i2c->irq = platform_get_irq(pdev, 0);
> + status = devm_request_irq(&pdev->dev, i2c->irq, hibvt_i2c_isr,
> + IRQF_SHARED, dev_name(&pdev->dev), i2c);
> + if (status) {
> + dev_dbg(i2c->dev, "falling back to polling mode");
> + i2c->irq = -1;
> + }
> +
> + adap = &i2c->adap;
> + i2c_set_adapdata(adap, i2c);
> + adap->owner = THIS_MODULE;
> + strlcpy(adap->name, "hibvt-i2c", sizeof(adap->name));
> + adap->dev.parent = &pdev->dev;
> + adap->dev.of_node = pdev->dev.of_node;
> + adap->algo = &hibvt_i2c_algo;
> +
> + /* Add the i2c adapter */
> + status = i2c_add_adapter(adap);
> + if (status) {
> + dev_err(i2c->dev, "failed to add bus to i2c core\n");
> + goto err_add_adapter;
> + }
> +
> + dev_info(i2c->dev, "%s%d@%dhz registered\n",
> + adap->name, adap->nr, i2c->freq);
> +
> + return 0;
> +
> +err_add_adapter:
> + clk_disable_unprepare(i2c->clk);
> + return status;
> +}
> +
> +static int hibvt_i2c_remove(struct platform_device *pdev)
> +{
> + struct hibvt_i2c_dev *i2c = platform_get_drvdata(pdev);
> +
> + clk_disable_unprepare(i2c->clk);
> + i2c_del_adapter(&i2c->adap);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int hibvt_i2c_suspend(struct device *dev)
> +{
> + struct hibvt_i2c_dev *i2c = dev_get_drvdata(dev);
> +
> + i2c_lock_adapter(&i2c->adap);
> + clk_disable_unprepare(i2c->clk);
> + i2c_unlock_adapter(&i2c->adap);
> +
> + return 0;
> +}
> +
> +static int hibvt_i2c_resume(struct device *dev)
> +{
> + struct hibvt_i2c_dev *i2c = dev_get_drvdata(dev);
> +
> + i2c_lock_adapter(&i2c->adap);
> + clk_prepare_enable(i2c->clk);
> + hibvt_i2c_hw_init(i2c);
> + i2c_unlock_adapter(&i2c->adap);
> +
> + return 0;
> +}
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(hibvt_i2c_dev_pm, hibvt_i2c_suspend,
> + hibvt_i2c_resume);
> +
> +static const struct of_device_id hibvt_i2c_match[] = {
> + { .compatible = "hisilicon,hibvt-i2c"},
> + { .compatible = "hisilicon,hi3516cv300-i2c"},
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, hibvt_i2c_match);
> +
> +static struct platform_driver hibvt_i2c_driver = {
> + .driver = {
> + .name = "hibvt-i2c",
> + .of_match_table = hibvt_i2c_match,
> + .pm = &hibvt_i2c_dev_pm,
> + },
> + .probe = hibvt_i2c_probe,
> + .remove = hibvt_i2c_remove,
> +};
> +
> +module_platform_driver(hibvt_i2c_driver);
> +
> +MODULE_AUTHOR("Pan Wen, <wenpan@hisilicon.com>");
> +MODULE_DESCRIPTION("HISILICON BVT I2C Bus driver");
> +MODULE_LICENSE("GPL v2");
>
^ permalink raw reply
* [patch v5 repost 1/1] i2c: add master driver for mellanox systems
From: vadimp @ 2016-11-16 7:08 UTC (permalink / raw)
To: wsa; +Cc: linux-i2c, linux-kernel, jiri, Vadim Pasternak, Michael Shych
From: Vadim Pasternak <vadimp@mellanox.com>
Device driver for Mellanox I2C controller logic, implemented in Lattice
CPLD device.
Device supports:
- Master mode
- One physical bus
- Polling mode
The Kconfig currently controlling compilation of this code is:
drivers/i2c/busses/Kconfig:config I2C_MLXCPLD
Signed-off-by: Michael Shych <michaelsh@mellanox.com>
Signed-off-by: Vadim Pasternak <vadimp@mellanox.com>
Reviewed-by: Jiri Pirko <jiri@mellanox.com>
Reviewed-by: Vladimir Zapolskiy <vz@mleia.com>
---
v4->v5:
Comments pointed out by Vladimir:
- Remove "default n" from Kconfig;
- Fix the comments for timeout and pool time;
- Optimize error flow in mlxcpld_i2c_probe;
v3->v4:
Comments pointed out by Vladimir:
- Set default to no in Kconfig;
- Make mlxcpld_i2c_plat_dev static and add empty line before the
declaration;
- In function mlxcpld_i2c_invalid_len remove (msg->len < 0), since len is
unsigned;
- Remove unused symbol mlxcpld_i2c_plat_dev;
- Remove extra spaces in comments to mlxcpld_i2c_check_msg_params;
- Remove unnecessary round braces in mlxcpld_i2c_set_transf_data;
- Remove the assignment of 'i' variable in mlxcpld_i2c_wait_for_tc;
- Add extra line in mlxcpld_i2c_xfer;
- Move assignment of the adapter's fields retries and nr inside
mlxcpld_i2c_adapter declaration;
v2->v3:
Comments pointed out by Vladimir:
- Use tab symbol as indentation in Kconfig
- Add the Kconfig section preserving the alphabetical order - added
within "Other I2C/SMBus bus drivers" after I2C_ELEKTOR (but after this
sections others are not follow alphabetical);
- Change license to dual;
- Replace ADRR with ADDR in macros;
- Remove unused macros: MLXCPLD_LPCI2C_LPF_DFLT,
MLXCPLD_LPCI2C_HALF_CYC_100, MLXCPLD_LPCI2C_I2C_HOLD_100,
MLXCPLD_LPCI2C_HALF_CYC_REG, MLXCPLD_LPCI2C_I2C_HOLD_REG;
- Fix checkpatch warnings (**/ and the end of comment);
- Add empty line before structures mlxcpld_i2c_regs,
mlxcpld_i2c_curr_transf, mlxcpld_i2c_priv;
- Remove unused structure mlxcpld_i2c_regs;
- Remove from mlxcpld_i2c_priv the next fields:
retr_num, poll_time, block_sz, xfer_to; use instead macros
respectively: MLXCPLD_I2C_RETR_NUM, MLXCPLD_I2C_POLL_TIME,
MLXCPLD_I2C_DATA_REG_SZ, MLXCPLD_I2C_XFER_TO;
- In mlxcpld_i2c_invalid_len remove unnecessary else;
- Optimize mlxcpld_i2c_set_transf_data;
- mlxcpld_i2c_reset - add empty lines after/before mutex
lock/unlock;
- mlxcpld_i2c_wait_for_free - cover case timeout is equal
MLXCPLD_I2C_XFER_TO;
- mlxcpld_i2c_wait_for_tc:
- Do not assign err in declaration (also err is removed);
- Insert empty line before case MLXCPLD_LPCI2C_ACK_IND;
- inside case MLXCPLD_LPCI2C_ACK_IND - avoid unnecessary
indentation;
- Remove case MLXCPLD_LPCI2C_ERR_IND and remove this macro;
- Add empty lines in mlxcpld_i2c_xfer before/after mutex_lock/
mutex_unlock;
- In mlxcpld_i2c_probe add emtpy line after platform_set_drvdata;
- Replace platfrom handle pdev in mlxcpld_i2c_priv with the pointer
to the structure device;
- Place assignment of base_addr near the others;
- Enclose e-mail with <>;
Fixes added by Vadim:
- Change structure description format according to
Documentation/kernel-documentation.rst guideline;
- mlxcpld_i2c_wait_for_tc: return error if status reaches default case;
v1->v2
Fixes added by Vadim:
- Put new record in Makefile in alphabetic order;
- Remove http://www.mellanox.com from MAINTAINERS record;
---
Documentation/i2c/busses/i2c-mlxcpld | 47 +++
MAINTAINERS | 8 +
drivers/i2c/busses/Kconfig | 11 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-mlxcpld.c | 551 +++++++++++++++++++++++++++++++++++
5 files changed, 618 insertions(+)
create mode 100644 Documentation/i2c/busses/i2c-mlxcpld
create mode 100644 drivers/i2c/busses/i2c-mlxcpld.c
diff --git a/Documentation/i2c/busses/i2c-mlxcpld b/Documentation/i2c/busses/i2c-mlxcpld
new file mode 100644
index 0000000..0f8678a
--- /dev/null
+++ b/Documentation/i2c/busses/i2c-mlxcpld
@@ -0,0 +1,47 @@
+Driver i2c-mlxcpld
+
+Author: Michael Shych <michaelsh@mellanox.com>
+
+This is a for Mellanox I2C controller logic, implemented in Lattice CPLD
+device.
+Device supports:
+ - Master mode.
+ - One physical bus.
+ - Polling mode.
+
+This controller is equipped within the next Mellanox systems:
+"msx6710", "msx6720", "msb7700", "msn2700", "msx1410", "msn2410", "msb7800",
+"msn2740", "msn2100".
+
+The next transaction types are supported:
+ - Receive Byte/Block.
+ - Send Byte/Block.
+ - Read Byte/Block.
+ - Write Byte/Block.
+
+Registers:
+CTRL 0x1 - control reg.
+ Resets all the registers.
+HALF_CYC 0x4 - cycle reg.
+ Configure the width of I2C SCL half clock cycle (in 4 LPC_CLK
+ units).
+I2C_HOLD 0x5 - hold reg.
+ OE (output enable) is delayed by value set to this register
+ (in LPC_CLK units)
+CMD 0x6 - command reg.
+ Bit 7(lsb), 0 = write, 1 = read.
+ Bits [6:0] - the 7bit Address of the I2C device.
+ It should be written last as it triggers an I2C transaction.
+NUM_DATA 0x7 - data size reg.
+ Number of address bytes to write in read transaction
+NUM_ADDR 0x8 - address reg.
+ Number of address bytes to write in read transaction.
+STATUS 0x9 - status reg.
+ Bit 0 - transaction is completed.
+ Bit 4 - ACK/NACK.
+DATAx 0xa - 0x54 - 68 bytes data buffer regs.
+ For write transaction address is specified in four first bytes
+ (DATA1 - DATA4), data starting from DATA4.
+ For read transactions address is send in separate transaction and
+ specified in four first bytes (DATA0 - DATA3). Data is reading
+ starting from DATA0.
diff --git a/MAINTAINERS b/MAINTAINERS
index 411e3b8..26d05f8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7881,6 +7881,14 @@ W: http://www.mellanox.com
Q: http://patchwork.ozlabs.org/project/netdev/list/
F: drivers/net/ethernet/mellanox/mlxsw/
+MELLANOX MLXCPLD I2C DRIVER
+M: Vadim Pasternak <vadimp@mellanox.com>
+M: Michael Shych <michaelsh@mellanox.com>
+L: linux-i2c@vger.kernel.org
+S: Supported
+F: drivers/i2c/busses/i2c-mlxcpld.c
+F: Documentation/i2c/busses/i2c-mlxcpld
+
MELLANOX MLXCPLD LED DRIVER
M: Vadim Pasternak <vadimp@mellanox.com>
L: linux-leds@vger.kernel.org
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d252276..6399cea 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1150,6 +1150,17 @@ config I2C_ELEKTOR
This support is also available as a module. If so, the module
will be called i2c-elektor.
+config I2C_MLXCPLD
+ tristate "Mellanox I2C driver"
+ depends on X86_64
+ help
+ This exposes the Mellanox platform I2C busses to the linux I2C layer
+ for X86 based systems.
+ Controller is implemented as CPLD logic.
+
+ This driver can also be built as a module. If so, the module will be
+ called as i2c-mlxcpld.
+
config I2C_PCA_ISA
tristate "PCA9564/PCA9665 on an ISA bus"
depends on ISA
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 29764cc..645bf08 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_I2C_BCM_KONA) += i2c-bcm-kona.o
obj-$(CONFIG_I2C_BRCMSTB) += i2c-brcmstb.o
obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += i2c-cros-ec-tunnel.o
obj-$(CONFIG_I2C_ELEKTOR) += i2c-elektor.o
+obj-$(CONFIG_I2C_MLXCPLD) += i2c-mlxcpld.o
obj-$(CONFIG_I2C_OPAL) += i2c-opal.o
obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o
obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o
diff --git a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c
new file mode 100644
index 0000000..f7a6f3a
--- /dev/null
+++ b/drivers/i2c/busses/i2c-mlxcpld.c
@@ -0,0 +1,551 @@
+/*
+ * drivers/i2c/busses/i2c-mlxcpld.c
+ * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
+ * Copyright (c) 2016 Michael Shych <michaels@mellanox.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the names of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/* General defines */
+#define MLXPLAT_CPLD_LPC_I2C_BASE_ADDR 0x2000
+#define MLXCPLD_I2C_DEVICE_NAME "i2c_mlxcpld"
+#define MLXCPLD_I2C_VALID_FLAG (I2C_M_RECV_LEN | I2C_M_RD)
+#define MLXCPLD_I2C_BUS_NUM 1
+#define MLXCPLD_I2C_DATA_REG_SZ 36
+#define MLXCPLD_I2C_MAX_ADDR_LEN 4
+#define MLXCPLD_I2C_RETR_NUM 2
+#define MLXCPLD_I2C_XFER_TO 500000 /* usec */
+#define MLXCPLD_I2C_POLL_TIME 2000 /* usec */
+
+/* LPC I2C registers */
+#define MLXCPLD_LPCI2C_LPF_REG 0x0
+#define MLXCPLD_LPCI2C_CTRL_REG 0x1
+#define MLXCPLD_LPCI2C_HALF_CYC_REG 0x4
+#define MLXCPLD_LPCI2C_I2C_HOLD_REG 0x5
+#define MLXCPLD_LPCI2C_CMD_REG 0x6
+#define MLXCPLD_LPCI2C_NUM_DAT_REG 0x7
+#define MLXCPLD_LPCI2C_NUM_ADDR_REG 0x8
+#define MLXCPLD_LPCI2C_STATUS_REG 0x9
+#define MLXCPLD_LPCI2C_DATA_REG 0xa
+
+/* LPC I2C masks and parametres */
+#define MLXCPLD_LPCI2C_RST_SEL_MASK 0x1
+#define MLXCPLD_LPCI2C_TRANS_END 0x1
+#define MLXCPLD_LPCI2C_STATUS_NACK 0x10
+#define MLXCPLD_LPCI2C_NO_IND 0
+#define MLXCPLD_LPCI2C_ACK_IND 1
+#define MLXCPLD_LPCI2C_NACK_IND 2
+
+/**
+ * struct mlxcpld_i2c_curr_transf - current transaction parameters:
+ * @cmd: command;
+ * @addr_width: address width;
+ * @data_len: data length;
+ * @cmd: command register;
+ * @msg_num: message number;
+ * @msg: pointer to message buffer;
+ */
+
+struct mlxcpld_i2c_curr_transf {
+ u8 cmd;
+ u8 addr_width;
+ u8 data_len;
+ u8 msg_num;
+ struct i2c_msg *msg;
+};
+
+/**
+ * struct mlxcpld_i2c_priv - private controller data:
+ * @lpc_gen_dec_reg: register space;
+ * @adap: i2c adapter;
+ * @dev_id: device id;
+ * @base_addr: base IO address;
+ * @lock: bus access lock;
+ * @xfer: current i2c transfer block;
+ * @dev: device handle;
+ */
+
+struct mlxcpld_i2c_priv {
+ struct i2c_adapter adap;
+ u16 dev_id;
+ u16 base_addr;
+ struct mutex lock;
+ struct mlxcpld_i2c_curr_transf xfer;
+ struct device *dev;
+};
+
+static void mlxcpld_i2c_lpc_write_buf(u8 *data, u8 len, u32 addr)
+{
+ int i, nbyte, ndword;
+
+ nbyte = len % 4;
+ ndword = len / 4;
+ for (i = 0; i < ndword; i++)
+ outl(*((u32 *)data + i), addr + i * 4);
+ ndword *= 4;
+ addr += ndword;
+ data += ndword;
+ for (i = 0; i < nbyte; i++)
+ outb(*(data + i), addr + i);
+}
+
+static void mlxcpld_i2c_lpc_read_buf(u8 *data, u8 len, u32 addr)
+{
+ int i, nbyte, ndword;
+
+ nbyte = len % 4;
+ ndword = len / 4;
+ for (i = 0; i < ndword; i++)
+ *((u32 *)data + i) = inl(addr + i * 4);
+ ndword *= 4;
+ addr += ndword;
+ data += ndword;
+ for (i = 0; i < nbyte; i++)
+ *(data + i) = inb(addr + i);
+}
+
+static void mlxcpld_i2c_read_comm(struct mlxcpld_i2c_priv *priv, u8 offs,
+ u8 *data, u8 datalen)
+{
+ u32 addr = priv->base_addr + offs;
+
+ switch (datalen) {
+ case 1:
+ *(data) = inb(addr);
+ break;
+ case 2:
+ *((u16 *)data) = inw(addr);
+ break;
+ case 3:
+ *((u16 *)data) = inw(addr);
+ *(data + 2) = inb(addr + 2);
+ break;
+ case 4:
+ *((u32 *)data) = inl(addr);
+ break;
+ default:
+ mlxcpld_i2c_lpc_read_buf(data, datalen, addr);
+ break;
+ }
+}
+
+static void mlxcpld_i2c_write_comm(struct mlxcpld_i2c_priv *priv, u8 offs,
+ u8 *data, u8 datalen)
+{
+ u32 addr = priv->base_addr + offs;
+
+ switch (datalen) {
+ case 1:
+ outb(*(data), addr);
+ break;
+ case 2:
+ outw(*((u16 *)data), addr);
+ break;
+ case 3:
+ outw(*((u16 *)data), addr);
+ outb(*(data + 2), addr + 2);
+ break;
+ case 4:
+ outl(*((u32 *)data), addr);
+ break;
+ default:
+ mlxcpld_i2c_lpc_write_buf(data, datalen, addr);
+ break;
+ }
+}
+
+/* Check validity of current i2c message and all transfer.
+ * Calculate also coomon length of all i2c messages in transfer.
+ */
+static int mlxcpld_i2c_invalid_len(struct mlxcpld_i2c_priv *priv,
+ const struct i2c_msg *msg, u8 *comm_len)
+{
+ u8 max_len = msg->flags == I2C_M_RD ? MLXCPLD_I2C_DATA_REG_SZ -
+ MLXCPLD_I2C_MAX_ADDR_LEN : MLXCPLD_I2C_DATA_REG_SZ;
+
+ if (msg->len > max_len)
+ return -EINVAL;
+
+ *comm_len += msg->len;
+ if (*comm_len > MLXCPLD_I2C_DATA_REG_SZ)
+ return -EINVAL;
+
+ return 0;
+}
+
+/* Check validity of received i2c messages parameters.
+ * Returns 0 if OK, other - in case of invalid parameters
+ * or common length of data that should be passed to CPLD
+ */
+static int mlxcpld_i2c_check_msg_params(struct mlxcpld_i2c_priv *priv,
+ struct i2c_msg *msgs, int num,
+ u8 *comm_len)
+{
+ int i;
+
+ if (!num) {
+ dev_err(priv->dev, "Incorrect 0 num of messages\n");
+ return -EINVAL;
+ }
+
+ if (unlikely(msgs[0].addr > 0x7f)) {
+ dev_err(priv->dev, "Invalid address 0x%03x\n",
+ msgs[0].addr);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num; ++i) {
+ if (unlikely(!msgs[i].buf)) {
+ dev_err(priv->dev, "Invalid buf in msg[%d]\n",
+ i);
+ return -EINVAL;
+ }
+ if (unlikely(msgs[0].addr != msgs[i].addr)) {
+ dev_err(priv->dev, "Invalid addr in msg[%d]\n",
+ i);
+ return -EINVAL;
+ }
+ if (unlikely(mlxcpld_i2c_invalid_len(priv, &msgs[i],
+ comm_len))) {
+ dev_err(priv->dev, "Invalid len %d msg[%d], addr 0x%x, lag %u\n",
+ msgs[i].len, i, msgs[i].addr, msgs[i].flags);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* Check if transfer is completed and status of operation.
+ * Returns 0 - transfer completed (both ACK or NACK),
+ * negative - transfer isn't finished.
+ */
+static int mlxcpld_i2c_check_status(struct mlxcpld_i2c_priv *priv, int *status)
+{
+ u8 val;
+
+ mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_STATUS_REG, &val, 1);
+
+ if (val & MLXCPLD_LPCI2C_TRANS_END) {
+ if (val & MLXCPLD_LPCI2C_STATUS_NACK)
+ /* The slave is unable to accept the data. No such
+ * slave, command not understood, or unable to accept
+ * any more data.
+ */
+ *status = MLXCPLD_LPCI2C_NACK_IND;
+ else
+ *status = MLXCPLD_LPCI2C_ACK_IND;
+ return 0;
+ }
+ *status = MLXCPLD_LPCI2C_NO_IND;
+
+ return -EIO;
+}
+
+static void mlxcpld_i2c_set_transf_data(struct mlxcpld_i2c_priv *priv,
+ struct i2c_msg *msgs, int num,
+ u8 comm_len)
+{
+ priv->xfer.msg = msgs;
+ priv->xfer.msg_num = num;
+
+ /*
+ * All upper layers currently are never use transfer with more than
+ * 2 messages. Actually, it's also not so relevant in Mellanox systems
+ * because of HW limitation. Max size of transfer is o more than 20B
+ * in current x86 LPCI2C bridge.
+ */
+ priv->xfer.cmd = msgs[num - 1].flags & I2C_M_RD;
+
+ if (priv->xfer.cmd == I2C_M_RD && comm_len != msgs[0].len) {
+ priv->xfer.addr_width = msgs[0].len;
+ priv->xfer.data_len = comm_len - priv->xfer.addr_width;
+ } else {
+ priv->xfer.addr_width = 0;
+ priv->xfer.data_len = comm_len;
+ }
+}
+
+/* Reset CPLD LPCI2C block */
+static void mlxcpld_i2c_reset(struct mlxcpld_i2c_priv *priv)
+{
+ u8 val;
+
+ mutex_lock(&priv->lock);
+
+ mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_CTRL_REG, &val, 1);
+ val &= ~MLXCPLD_LPCI2C_RST_SEL_MASK;
+ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_CTRL_REG, &val, 1);
+
+ mutex_unlock(&priv->lock);
+}
+
+/* Make sure the CPLD is ready to start transmitting.
+ * Return 0 if it is, -EBUSY if it is not.
+ */
+static int mlxcpld_i2c_check_busy(struct mlxcpld_i2c_priv *priv)
+{
+ u8 val;
+
+ mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_STATUS_REG, &val, 1);
+
+ if (val & MLXCPLD_LPCI2C_TRANS_END)
+ return 0;
+
+ return -EIO;
+}
+
+static int mlxcpld_i2c_wait_for_free(struct mlxcpld_i2c_priv *priv)
+{
+ int timeout = 0;
+
+ do {
+ if (!mlxcpld_i2c_check_busy(priv))
+ break;
+ usleep_range(MLXCPLD_I2C_POLL_TIME / 2, MLXCPLD_I2C_POLL_TIME);
+ timeout += MLXCPLD_I2C_POLL_TIME;
+ } while (timeout <= MLXCPLD_I2C_XFER_TO);
+
+ if (timeout > MLXCPLD_I2C_XFER_TO)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+/*
+ * Wait for master transfer to complete.
+ * It puts current process to sleep until we get interrupt or timeout expires.
+ * Returns the number of transferred or read bytes or error (<0).
+ */
+static int mlxcpld_i2c_wait_for_tc(struct mlxcpld_i2c_priv *priv)
+{
+ int status, i, timeout = 0;
+ u8 datalen;
+
+ do {
+ usleep_range(MLXCPLD_I2C_POLL_TIME / 2, MLXCPLD_I2C_POLL_TIME);
+ if (!mlxcpld_i2c_check_status(priv, &status))
+ break;
+ timeout += MLXCPLD_I2C_POLL_TIME;
+ } while (status == 0 && timeout < MLXCPLD_I2C_XFER_TO);
+
+ switch (status) {
+ case MLXCPLD_LPCI2C_NO_IND:
+ return -ETIMEDOUT;
+
+ case MLXCPLD_LPCI2C_ACK_IND:
+ if (priv->xfer.cmd != I2C_M_RD)
+ return (priv->xfer.addr_width + priv->xfer.data_len);
+
+ if (priv->xfer.msg_num == 1)
+ i = 0;
+ else
+ i = 1;
+
+ if (!priv->xfer.msg[i].buf)
+ return -EINVAL;
+
+ /*
+ * Actual read data len will be always the same as
+ * requested len. 0xff (line pull-up) will be returned
+ * if slave has no data to return. Thus don't read
+ * MLXCPLD_LPCI2C_NUM_DAT_REG reg from CPLD.
+ */
+ datalen = priv->xfer.data_len;
+
+ mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_DATA_REG,
+ priv->xfer.msg[i].buf, datalen);
+
+ return datalen;
+
+ case MLXCPLD_LPCI2C_NACK_IND:
+ return -EAGAIN;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void mlxcpld_i2c_xfer_msg(struct mlxcpld_i2c_priv *priv)
+{
+ int i, len = 0;
+ u8 cmd;
+
+ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_NUM_DAT_REG,
+ &priv->xfer.data_len, 1);
+ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_NUM_ADDR_REG,
+ &priv->xfer.addr_width, 1);
+
+ for (i = 0; i < priv->xfer.msg_num; i++) {
+ if ((priv->xfer.msg[i].flags & I2C_M_RD) != I2C_M_RD) {
+ /* Don't write to CPLD buffer in read transaction */
+ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_DATA_REG +
+ len, priv->xfer.msg[i].buf,
+ priv->xfer.msg[i].len);
+ len += priv->xfer.msg[i].len;
+ }
+ }
+
+ /* Set target slave address with command for master transfer.
+ * It should be latest executed function before CPLD transaction.
+ */
+ cmd = (priv->xfer.msg[0].addr << 1) | priv->xfer.cmd;
+ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_CMD_REG, &cmd, 1);
+}
+
+/* Generic lpc-i2c transfer.
+ * Returns the number of processed messages or error (<0).
+ */
+static int mlxcpld_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+ int num)
+{
+ struct mlxcpld_i2c_priv *priv = i2c_get_adapdata(adap);
+ u8 comm_len = 0;
+ int err;
+
+ err = mlxcpld_i2c_check_msg_params(priv, msgs, num, &comm_len);
+ if (err) {
+ dev_err(priv->dev, "Incorrect message\n");
+ return err;
+ }
+
+ /* Check bus state */
+ if (mlxcpld_i2c_wait_for_free(priv)) {
+ dev_err(priv->dev, "LPCI2C bridge is busy\n");
+
+ /*
+ * Usually it means something serious has happened.
+ * We can not have unfinished previous transfer
+ * so it doesn't make any sense to try to stop it.
+ * Probably we were not able to recover from the
+ * previous error.
+ * The only reasonable thing - is soft reset.
+ */
+ mlxcpld_i2c_reset(priv);
+ if (mlxcpld_i2c_check_busy(priv)) {
+ dev_err(priv->dev, "LPCI2C bridge is busy after reset\n");
+ return -EIO;
+ }
+ }
+
+ mlxcpld_i2c_set_transf_data(priv, msgs, num, comm_len);
+
+ mutex_lock(&priv->lock);
+
+ /* Do real transfer. Can't fail */
+ mlxcpld_i2c_xfer_msg(priv);
+
+ /* Wait for transaction complete */
+ err = mlxcpld_i2c_wait_for_tc(priv);
+
+ mutex_unlock(&priv->lock);
+
+ return err < 0 ? err : num;
+}
+
+static u32 mlxcpld_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm mlxcpld_i2c_algo = {
+ .master_xfer = mlxcpld_i2c_xfer,
+ .functionality = mlxcpld_i2c_func
+};
+
+static struct i2c_adapter mlxcpld_i2c_adapter = {
+ .owner = THIS_MODULE,
+ .name = "i2c-mlxcpld",
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &mlxcpld_i2c_algo,
+ .retries = MLXCPLD_I2C_RETR_NUM,
+ .nr = MLXCPLD_I2C_BUS_NUM,
+};
+
+static int mlxcpld_i2c_probe(struct platform_device *pdev)
+{
+ struct mlxcpld_i2c_priv *priv;
+ int err;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct mlxcpld_i2c_priv),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mutex_init(&priv->lock);
+ platform_set_drvdata(pdev, priv);
+
+ priv->dev = &pdev->dev;
+
+ /* Register with i2c layer */
+ mlxcpld_i2c_adapter.timeout = usecs_to_jiffies(MLXCPLD_I2C_XFER_TO);
+ priv->adap = mlxcpld_i2c_adapter;
+ priv->adap.dev.parent = &pdev->dev;
+ priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADDR;
+ i2c_set_adapdata(&priv->adap, priv);
+
+ err = i2c_add_numbered_adapter(&priv->adap);
+ if (err)
+ mutex_destroy(&priv->lock);
+
+ return err;
+}
+
+static int mlxcpld_i2c_remove(struct platform_device *pdev)
+{
+ struct mlxcpld_i2c_priv *priv = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&priv->adap);
+ mutex_destroy(&priv->lock);
+
+ return 0;
+}
+
+static struct platform_driver mlxcpld_i2c_driver = {
+ .probe = mlxcpld_i2c_probe,
+ .remove = mlxcpld_i2c_remove,
+ .driver = {
+ .name = MLXCPLD_I2C_DEVICE_NAME,
+ },
+};
+
+module_platform_driver(mlxcpld_i2c_driver);
+
+MODULE_AUTHOR("Michael Shych <michaels@mellanox.com>");
+MODULE_DESCRIPTION("Mellanox I2C-CPLD controller driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("platform:i2c-mlxcpld");
--
2.1.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox