* Re: [PATCH v3 2/2] Documentation: tpm: add the Physical TPM device tree binding documentation
From: Jarkko Sakkinen @ 2016-11-04 7:32 UTC (permalink / raw)
To: Rob Herring
Cc: Nayna Jain, devicetree-u79uwXL29TY76Z2rM5mHXA,
tpmdd-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
wsa-z923LK4zBo2bacvFa/9K2g, pawel.moll-5wv7dgnIgG8,
mark.rutland-5wv7dgnIgG8, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg,
galak-sgV2jX0FEOL9JmXXK+q4OQ, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
peterhuewe-Mmb7MZpHnFY, tpmdd-yWjUBOtONefk1uMJSBkQmQ,
jgunthorpe-ePGOBjL8dl3ta4EC/59zMFaTQe2KTcn/,
hellerda-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8,
ltcgcw-r/Jw6+rmf7HQT0dZR+AlfA,
cclaudio-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8,
honclo-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8
In-Reply-To: <20161031040245.cwnt4kelrm7liaga@rob-hp-laptop>
On Sun, Oct 30, 2016 at 11:02:45PM -0500, Rob Herring wrote:
> On Wed, Oct 26, 2016 at 05:21:45AM -0400, Nayna Jain wrote:
> > Newly added support of TPM 2.0 eventlog securityfs pseudo files in tpm
> > device driver consumes device tree bindings representing I2C based
> > Physical TPM. This patch adds the documentation for corresponding device
> > tree bindings of I2C based Physical TPM. These bindings are similar to
> > vtpm device tree bindings being used on IBM Power7+ and Power8 Systems
> > running PowerVM.
> >
> > Suggested-by: Jason Gunthorpe <jgunthorpe-ePGOBjL8dl3ta4EC/59zMFaTQe2KTcn/@public.gmane.org>
> > Signed-off-by: Nayna Jain <nayna-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
> > ---
> > Changelog v3:
> > - Added documentation for "label" property.
> >
> > Changelog v2:
> >
> > - Include review feedbacks.
> > - Move the doc within bindings/security/tpm.
> > - Add example for compatible property in description.
> > - Delete implicit properties like status, label from description.
> > - Redefine linux,sml-base description.
> >
> > .../devicetree/bindings/security/tpm/tpm-i2c.txt | 21 +++++++++++++++++++++
> > 1 file changed, 21 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/security/tpm/tpm-i2c.txt
>
> Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Applied.
/Jarkko
--
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 v3 1/2] Documentation: tpm: add the IBM Virtual TPM device tree binding documentation
From: Jarkko Sakkinen @ 2016-11-04 7:32 UTC (permalink / raw)
To: Rob Herring
Cc: mark.rutland-5wv7dgnIgG8, devicetree-u79uwXL29TY76Z2rM5mHXA,
ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, pawel.moll-5wv7dgnIgG8,
wsa-z923LK4zBo2bacvFa/9K2g,
honclo-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8,
tpmdd-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
linux-i2c-u79uwXL29TY76Z2rM5mHXA, galak-sgV2jX0FEOL9JmXXK+q4OQ,
cclaudio-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8
In-Reply-To: <20161031034722.irs7cp5pvhpcidur@rob-hp-laptop>
On Sun, Oct 30, 2016 at 10:47:23PM -0500, Rob Herring wrote:
> On Wed, Oct 26, 2016 at 05:21:44AM -0400, Nayna Jain wrote:
> > Virtual TPM, which is being used on IBM POWER7+ and POWER8 systems running
> > POWERVM, is currently supported by tpm device driver but lacks the
> > documentation. This patch adds the missing documentation for the existing
> > support.
> >
> > Suggested-by: Jason Gunthorpe <jgunthorpe-ePGOBjL8dl3ta4EC/59zMFaTQe2KTcn/@public.gmane.org>
> > Signed-off-by: Nayna Jain <nayna-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
> > ---
> > Changelog v3:
> >
> > - No changes done.
> >
> > Changelog v2:
> >
> > - New Patch.
> >
> > .../devicetree/bindings/security/tpm/ibmvtpm.txt | 41 ++++++++++++++++++++++
> > 1 file changed, 41 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/security/tpm/ibmvtpm.txt
>
> Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Applied.
/Jarkko
------------------------------------------------------------------------------
Developer Access Program for Intel Xeon Phi Processors
Access to Intel Xeon Phi processor-based developer platforms.
With one year of Intel Parallel Studio XE.
Training and support from Colfax.
Order your platform today. http://sdm.link/xeonphi
^ permalink raw reply
* [patch v4 1/1] i2c: add master driver for mellanox systems
From: vadimp @ 2016-11-04 9:15 UTC (permalink / raw)
To: wsa; +Cc: vz, 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>
---
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 | 12 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-mlxcpld.c | 558 +++++++++++++++++++++++++++++++++++
5 files changed, 626 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 3d838cf..dfc6a0a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7876,6 +7876,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..b9e1c4c 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1150,6 +1150,18 @@ 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
+ default n
+ 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..6c9e022
--- /dev/null
+++ b/drivers/i2c/busses/i2c-mlxcpld.c
@@ -0,0 +1,558 @@
+/*
+ * 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 /* msec */
+#define MLXCPLD_I2C_POLL_TIME 2000 /* msec */
+
+/* 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) {
+ dev_err(&pdev->dev, "Failed to add %s adapter (%d)\n",
+ MLXCPLD_I2C_DEVICE_NAME, err);
+ goto fail_adapter;
+ }
+
+ return 0;
+
+fail_adapter:
+ 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
* Re: [patch v3 1/1] i2c: add master driver for mellanox systems
From: Vladimir Zapolskiy @ 2016-11-03 22:48 UTC (permalink / raw)
To: vadimp, wsa; +Cc: linux-i2c, linux-kernel, jiri, Michael Shych
In-Reply-To: <4708bff8-db23-e9c3-197d-ab337affa080@mleia.com>
On 04.11.2016 00:34, Vladimir Zapolskiy wrote:
>> +
>> +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 */
>> + priv->adap = mlxcpld_i2c_adapter;
See a correction below.
>> + priv->adap.dev.parent = &pdev->dev;
>> + priv->adap.retries = MLXCPLD_I2C_RETR_NUM;
>> + priv->adap.nr = MLXCPLD_I2C_BUS_NUM;
>
> So since you want to allow the registration of only one such controller
> on a board (by the way in the opposite case it is also expected that
> "struct i2c_adapter" is allocated dynamically to avoid rewriting of
> data), you probably know the good reason behind it.
I've just noticed that you do a struct copy-on-assignment, while I think
it is very rarely used in the kernel sources (I don't know why), it cancels
my concern expressed above.
>
> But in that case you may consider to move .retries and .nr
> instantiation to the "static struct i2c_adapter" declaration above.
>
--
With best wishes,
Vladimir
^ permalink raw reply
* Re: [patch v3 1/1] i2c: add master driver for mellanox systems
From: Vladimir Zapolskiy @ 2016-11-03 22:34 UTC (permalink / raw)
To: vadimp, wsa; +Cc: linux-i2c, linux-kernel, jiri, Michael Shych
In-Reply-To: <1478199036-188306-1-git-send-email-vadimp@mellanox.com>
Hi Vadim,
please find some more review comments below.
On 03.11.2016 20:50, 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>
> ---
[snip]
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index d252276..be885d2 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -1150,6 +1150,18 @@ 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
> + default y
I believe the controller is not a widely used piece of hardware found
on every second laptop.
Please remove 'default y' line from the Kconfig record.
> + 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
[snip]
> +
> +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;
> +};
> +struct platform_device *mlxcpld_i2c_plat_dev;
This global variable is unused.
And it emits several static check warnings:
checkpatch --strict:
CHECK: Please use a blank line after function/struct/union/enum declarations
#324: FILE: drivers/i2c/busses/i2c-mlxcpld.c:110:
+};
+struct platform_device *mlxcpld_i2c_plat_dev;
smatch:
i2c-mlxcpld.c:110:24: warning: symbol 'mlxcpld_i2c_plat_dev' was not declared. Should it be static?
Please remove it as unused.
[snip]
> +
> +/* 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 < 0 || msg->len > max_len)
Compiler W=1 level warning:
i2c-mlxcpld.c: In function 'mlxcpld_i2c_invalid_len':
i2c-mlxcpld.c:201:15: warning: comparison is always false due to limited range of data type [-Wtype-limits]
if (msg->len < 0 || msg->len > max_len)
^
msg->len is unsigned.
> + 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
Sorry for nitpicking, you may consider to remove one of two spaces
after asterisks above.
[snip]
> +
> +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);
Round brackets are not needed above.
> +
> + 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;
> + }
> +}
> +
[snip]
> +/*
> + * 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 = 1, timeout = 0;
Please remove the assignment of 'i' variable, after update it is
not needed anymore.
> + 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;
> + }
> +}
> +
[snip]
> +
> +/* Generic lpc-i2c transfer.
Just for my information, how do you unwrap 'lpc' acronym found in the code?
> + * 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);
Please add an empty line here to improve readability.
> + /* 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,
> +};
> +
> +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 */
> + priv->adap = mlxcpld_i2c_adapter;
> + priv->adap.dev.parent = &pdev->dev;
> + priv->adap.retries = MLXCPLD_I2C_RETR_NUM;
> + priv->adap.nr = MLXCPLD_I2C_BUS_NUM;
So since you want to allow the registration of only one such controller
on a board (by the way in the opposite case it is also expected that
"struct i2c_adapter" is allocated dynamically to avoid rewriting of
data), you probably know the good reason behind it.
But in that case you may consider to move .retries and .nr
instantiation to the "static struct i2c_adapter" declaration above.
> + priv->adap.timeout = usecs_to_jiffies(MLXCPLD_I2C_XFER_TO);
> + priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADDR;
> + i2c_set_adapdata(&priv->adap, priv);
> +
> + err = i2c_add_numbered_adapter(&priv->adap);
> + if (err) {
> + dev_err(&pdev->dev, "Failed to add %s adapter (%d)\n",
> + MLXCPLD_I2C_DEVICE_NAME, err);
> + goto fail_adapter;
> + }
> +
> + return 0;
> +
> +fail_adapter:
> + 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");
>
Generally the driver looks good and simple.
--
With best wishes,
Vladimir
^ permalink raw reply
* [patch v3 1/1] i2c: add master driver for mellanox systems
From: vadimp @ 2016-11-03 18:50 UTC (permalink / raw)
To: wsa; +Cc: vz, 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>
---
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 | 12 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-mlxcpld.c | 558 +++++++++++++++++++++++++++++++++++
5 files changed, 626 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 3d838cf..dfc6a0a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7876,6 +7876,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..be885d2 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1150,6 +1150,18 @@ 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
+ default y
+ 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..e90abab
--- /dev/null
+++ b/drivers/i2c/busses/i2c-mlxcpld.c
@@ -0,0 +1,558 @@
+/*
+ * 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 /* msec */
+#define MLXCPLD_I2C_POLL_TIME 2000 /* msec */
+
+/* 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;
+};
+struct platform_device *mlxcpld_i2c_plat_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 < 0 || 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 = 1, 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,
+};
+
+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 */
+ priv->adap = mlxcpld_i2c_adapter;
+ priv->adap.dev.parent = &pdev->dev;
+ priv->adap.retries = MLXCPLD_I2C_RETR_NUM;
+ priv->adap.nr = MLXCPLD_I2C_BUS_NUM;
+ priv->adap.timeout = usecs_to_jiffies(MLXCPLD_I2C_XFER_TO);
+ priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADDR;
+ i2c_set_adapdata(&priv->adap, priv);
+
+ err = i2c_add_numbered_adapter(&priv->adap);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to add %s adapter (%d)\n",
+ MLXCPLD_I2C_DEVICE_NAME, err);
+ goto fail_adapter;
+ }
+
+ return 0;
+
+fail_adapter:
+ 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
* Re: [PATCH v4 1/6] Add Advantech iManager MFD core driver
From: Lee Jones @ 2016-11-03 8:41 UTC (permalink / raw)
To: Richard Vidal-Dorsch
Cc: linus.walleij, gnurou, jdelvare, linux, wsa, jingoohan1,
tomi.valkeinen, wim, linux-kernel, linux-gpio, linux-hwmon,
linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski, jo.sunga,
weilun.huang, andrew.chou
In-Reply-To: <20161102083751.6335-2-richard.dorsch@gmail.com>
On Wed, 02 Nov 2016, Richard Vidal-Dorsch wrote:
> This patch adds Advantech iManager Embedded Controller MFD core driver.
> This mfd core dirver provides an interface for GPIO, I2C, HWmon,
> Watchdog, and Backlight/Brightness control.
>
> Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
> ---
> drivers/mfd/Kconfig | 18 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/imanager-core.c | 941 ++++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/imanager-ec.h | 228 ++++++++++
> include/linux/mfd/imanager.h | 221 ++++++++++
> 5 files changed, 1409 insertions(+)
> create mode 100644 drivers/mfd/imanager-core.c
> create mode 100644 include/linux/mfd/imanager-ec.h
> create mode 100644 include/linux/mfd/imanager.h
After a brief review, it is my belief that this looks like a platform
driver, which should live in drivers/platform/.
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* RE: [patch v5] i2c: mux: mellanox: add driver
From: Vadim Pasternak @ 2016-11-03 5:20 UTC (permalink / raw)
To: Peter Rosin, wsa@the-dreams.de
Cc: linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org,
jiri@resnulli.us, Michael Shych
In-Reply-To: <c530714e-b61e-8a38-a054-9d335f0e8a71@axentia.se>
Hi,
I see that this patch has not been picked-up yet for i2c-next.
Is it possible it was missed from some reason?
Thanks,
Vadim.
> -----Original Message-----
> From: Vadim Pasternak
> Sent: Friday, September 23, 2016 2:57 PM
> To: 'Peter Rosin' <peda@axentia.se>; wsa@the-dreams.de
> Cc: linux-i2c@vger.kernel.org; linux-kernel@vger.kernel.org; jiri@resnulli.us;
> Michael Shych <michaelsh@mellanox.com>
> Subject: RE: [patch v5] i2c: mux: mellanox: add driver
>
>
>
> > -----Original Message-----
> > From: Peter Rosin [mailto:peda@axentia.se]
> > Sent: Friday, September 23, 2016 12:36 PM
> > To: Vadim Pasternak <vadimp@mellanox.com>; wsa@the-dreams.de
> > Cc: linux-i2c@vger.kernel.org; linux-kernel@vger.kernel.org;
> > jiri@resnulli.us; Michael Shych <michaelsh@mellanox.com>
> > Subject: Re: [patch v5] i2c: mux: mellanox: add driver
> >
> > On 2016-09-13 22:37, vadimp@mellanox.com wrote:
> > > From: Vadim Pasternak <vadimp@mellanox.com>
> > >
> > > This driver allows I2C routing controlled through CPLD select
> > > registers on a wide range of Mellanox systems (CPLD Lattice device).
> > > MUX selection is provided by digital and analog HW. Analog part is
> > > not under SW control.
> > > Digital part is under CPLD control (channel selection/de-selection).
> > >
> > > Connectivity schema.
> > > i2c-mlxcpld Digital Analog
> > > driver
> > > *--------* * -> mux1 (virt bus2) -> mux ->|
> > > | I2CLPC | i2c physical * -> mux2 (virt bus3) -> mux ->|
> > > | bridge | bus 1 *---------* |
> > > | logic |---------------------> * mux reg * |
> > > | in CPLD| *---------* |
> > > *--------* i2c-mux-mlxpcld ^ * -> muxn (virt busn) -> mux ->|
> > > | driver | |
> > > | *---------------* | Devices
> > > | * CPLD (i2c bus)* select |
> > > | * registers for *--------*
> > > | * mux selection * deselect
> > > | *---------------*
> > > | |
> > > <--------> <----------->
> > > i2c cntrl Board cntrl reg
> > > reg space space (mux select,
> > > IO, LED, WD, info)
> >
> > Hmm, I'm wondering about the above schematics, which seems a bit
> > overly complex. I mean, it's not relevant to this driver how the
> > I2CLPC bridge logic in the CPLD is controlled. Particularly so since
> > it does not need to be a this particular i2c adapter that is muxed.
> >
> > But what I'm really wondering is if this mux can mux same other bus
> > than is used to control the mux. I.e. is it possible to do something like this:
> >
> > .---. .-------------.
> > | | | |-- i2c2 --
> > | l |-- i2c0 --| mlxcpld mux |
> > | i | | |-- i2c3 --
> > | n | '-------------'
> > | u | |
> > | x |-- i2c1 ---------'
> > | |
> > '---'
> >
> > Or is it always as below, with the mux being controlled from the same
> > i2c bus it muxes?
> >
> > .---. .-------------.
> > | l | | |-- i2c1 --
> > | i |-- i2c0 --+--| mlxcpld mux |
> > | n | | | |-- i2c2 --
> > | u | | '-------------'
> > | x | | |
> > '---' '---------'
> >
>
> Hi Peter,
>
> Thank you very much for this and all previous feedbacks.
>
> Yes, it is not coupled with I2CLPC bridge logic.
> When I sent my initial patch, it was marked as 1/2, while 1/1 was patch for i2c-
> mlxcpld controller.
> So, maybe my comment here is redundant I can make it simpler.
>
> Actual description you provided is better.
>
> Would you like me to submit patch with this modification only?
>
>
> Just as an example to show how we are going to use this driver.
>
> This is x86 systems, when we do have LPCI2C as controller (we have on these
> systems management CPLD attached to LPC on carrier board and I2C attached
> CLPD on switch board:
> .---. .-------------.
> | l | | |-- i2cx1 -- i2cy8
> | i |-- i2c1 --+--| mlxcpld mux |
> | n | | | |-- i2y1 -- i2cy8
> | u | | '-------------'
> | x | | |
> '---' '---------'
>
> I2C CPLD is attached to bus 1 at 0x62, and has register 0x81 - 0x85 for access to
> 4 or 5 banks of QSFP ports (each bank of 8).
>
> This is coming systems equipped with BMC Aspeed 2520 SoC ARM11 based (by
> the way this driver now also in review process):
> .---. .-------------.
> | l | | |-- i2cx1 -- i2cy8
> | i |-- i2cn --+--| mlxcpld mux |
> | n | | | |-- i2y1 -- i2cy8
> | u | | '-------------'
> | x | | |
> '---' '---------
>
> I2C CPLD is attached to bus n (SoC equipped with 14 I2C busses) at 0x62, and has
> register 0xz1 - 0xz1 for access to 4 or 5 banks of QSFP ports (each bank of 8).
>
> Cheers,
> Vadim.
>
> > Cheers,
> > Peter
^ permalink raw reply
* RE: [patch v2] i2c: add master driver for mellanox systems
From: Vadim Pasternak @ 2016-11-03 4:53 UTC (permalink / raw)
To: Vladimir Zapolskiy, wsa@the-dreams.de
Cc: linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org,
jiri@resnulli.us, Michael Shych
In-Reply-To: <1bf3000c-0390-395b-8b8c-633c6a417e0e@mleia.com>
> -----Original Message-----
> From: Vladimir Zapolskiy [mailto:vz@mleia.com]
> Sent: Thursday, November 03, 2016 1:09 AM
> To: Vadim Pasternak <vadimp@mellanox.com>; wsa@the-dreams.de
> Cc: linux-i2c@vger.kernel.org; linux-kernel@vger.kernel.org; jiri@resnulli.us;
> Michael Shych <michaelsh@mellanox.com>
> Subject: Re: [patch v2] i2c: add master driver for mellanox systems
>
> Hi Vadim,
Hi Vladimir,
Thank you very much for you review.
>
> On 19.09.2016 18:00, 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>
> > v1->v2
> > Fixes added by Vadim:
> > - Put new record in Makefile in alphabetic order;
> > - Remove http://www.mellanox.com from MAINTAINERS record;
>
> the change does not apply cleanly on top of the recent source, please consider
> to rebase it.
I suppose this is because the patch has been sent quite long time ago and it was created in 4.8 i2c/for-next. I'll redo it in latest i2c/for-next branch.
I suppose this is also the reason of checkpatch warnings, since it passed clean the validation in 4.8.
>
> Anyway please consider to check some review points found below.
>
> > ---
> > Documentation/i2c/busses/i2c-mlxcpld | 47 +++
> > MAINTAINERS | 8 +
> > drivers/i2c/busses/Kconfig | 12 +
> > drivers/i2c/busses/Makefile | 1 +
> > drivers/i2c/busses/i2c-mlxcpld.c | 597
> +++++++++++++++++++++++++++++++++++
> > 5 files changed, 665 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 6781a3f..dc31231 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -7667,6 +7667,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
> > +
> > SOFT-ROCE DRIVER (rxe)
> > M: Moni Shoua <monis@mellanox.com>
> > L: linux-rdma@vger.kernel.org
> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> > index 5c3993b..1126142a 100644
> > --- a/drivers/i2c/busses/Kconfig
> > +++ b/drivers/i2c/busses/Kconfig
> > @@ -1203,4 +1203,16 @@ config I2C_OPAL
> > This driver can also be built as a module. If so, the module will be
> > called as i2c-opal.
> >
> > +config I2C_MLXCPLD
> > + tristate "Mellanox I2C driver"
> > + depends on X86_64
> > + default y
> > + help
>
> Please use one tab symbol as indentation above.
>
> Please add the section preserving the alphabetical order.
>
> > + 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.
> > +
> > endmenu
> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> > index 37f2819..4df3578 100644
> > --- a/drivers/i2c/busses/Makefile
> > +++ b/drivers/i2c/busses/Makefile
> > @@ -118,5 +118,6 @@ obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o
> > obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o
> > obj-$(CONFIG_I2C_XGENE_SLIMPRO) += i2c-xgene-slimpro.o
> > obj-$(CONFIG_SCx200_ACB) += scx200_acb.o
> > +obj-$(CONFIG_I2C_MLXCPLD) += i2c-mlxcpld.o
>
> Please add the line preserving the alphabetical order.
>
> >
> > ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG diff --git
> > a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c
> > new file mode 100644
> > index 0000000..dd62190
> > --- /dev/null
> > +++ b/drivers/i2c/busses/i2c-mlxcpld.c
> > @@ -0,0 +1,597 @@
> > +/*
> > + * 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.
>
> So the code is dual licensed under BSD 3-Clause / GPLv2.
>
> > + */
> > +
> > +#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_ADRR 0x2000
>
> s/ADRR/ADDR/
>
> > +#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 /* msec */
> > +#define MLXCPLD_I2C_POLL_TIME 2000 /* msec */
> > +
> > +/* 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
>
> This macro is not used.
>
> > +#define MLXCPLD_LPCI2C_LPF_DFLT 0x2
>
> This macro is not used.
>
> > +#define MLXCPLD_LPCI2C_HALF_CYC_100 0x1f
>
> This macro is not used.
>
> > +#define MLXCPLD_LPCI2C_I2C_HOLD_100 0x3c
>
> This macro is not used.
>
> > +#define MLXCPLD_LPCI2C_TRANS_END 0x1
> > +#define MLXCPLD_LPCI2C_STATUS_NACK 0x10
>
> This macro is not used.
>
> > +#define MLXCPLD_LPCI2C_ERR_IND -1
>
> This macro is not used.
>
> > +#define MLXCPLD_LPCI2C_NO_IND 0
> > +#define MLXCPLD_LPCI2C_ACK_IND 1
> > +#define MLXCPLD_LPCI2C_NACK_IND 2
> > +
> > +/**
> > + * mlxcpld_i2c_regs - controller registers:
> > + * @half_cyc - half cycle register
> > + * @i2c_hold - hold register
> > + * @config - config register
> > + * @cmd - command register
> > + * @cmd - status register
> > + * @data - register data
> > +**/
>
> checkpatch warning:
>
> WARNING: Block comments should align the * on each line
> #85: FILE: drivers/i2c/busses/i2c-mlxcpld.c:85:
> + * @data - register data
> +**/
>
> Also for improving readability please add an empty line before the following
> struct declaration.
>
> > +struct mlxcpld_i2c_regs {
> > + u8 half_cyc;
> > + u8 i2c_hold;
> > + u8 config;
> > + u8 cmd;
> > + u8 status;
> > + u8 data[MLXCPLD_I2C_DATA_REG_SZ];
> > +};
> > +
>
> This structure is not used, please remove it.
>
> > +/**
> > + * 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
> > +**/
>
> checkpatch warning:
>
> WARNING: Block comments should align the * on each line
> #103: FILE: drivers/i2c/busses/i2c-mlxcpld.c:103:
> + * @msg - pointer to message buffer
> +**/
>
> Also for improving readability please add an empty line before the following
> struct declaration.
>
> > +struct mlxcpld_i2c_curr_transf {
> > + u8 cmd;
> > + u8 addr_width;
> > + u8 data_len;
> > + u8 msg_num;
> > + struct i2c_msg *msg;
> > +};
> > +
> > +/**
> > + * mlxcpld_i2c_priv - private controller data:
> > + * @lpc_gen_dec_reg - register space
> > + * @adap - i2c adapter
> > + * @dev_id - device id
> > + * @base_addr - base IO address
> > + * @poll_time - polling time
> > + * @xfer_to - transfer timeout in microsec (500000)
> > + * @retr_num - access retries number (2)
> > + * @block_sz - maximum data block size (36),
> > + * @lock - bus access lock
> > + * @lpc_i2c_res - lpc i2c resourse
> > + * @lpc_cpld_res - lpc cpld resource
> > + * @xfer - current i2c transfer block
> > + * @pdev - platform device
> > +**/
>
> WARNING: Block comments should align the * on each line
> #127: FILE: drivers/i2c/busses/i2c-mlxcpld.c:127:
> + * @pdev - platform device
> +**/
>
> Also for improving readability please add an empty line before the following
> struct declaration.
>
> > +struct mlxcpld_i2c_priv {
> > + struct i2c_adapter adap;
> > + u16 dev_id;
> > + u16 base_addr;
> > + u16 poll_time;
> > + int xfer_to;
> > + int retr_num;
>
> Remove 'retr_num', set MLXCPLD_I2C_RETR_NUM directly.
>
> Alsoe see my comment for .probe() function.
>
> > + int block_sz;
> > + struct mutex lock;
> > + struct mlxcpld_i2c_curr_transf xfer;
> > + struct platform_device *pdev;
> > +};
>
> checkpatch complains:
>
> CHECK: Please use a blank line after function/struct/union/enum declarations
> #140: FILE: drivers/i2c/busses/i2c-mlxcpld.c:140:
> +};
> +struct platform_device *mlxcpld_i2c_plat_dev;
>
> > +struct platform_device *mlxcpld_i2c_plat_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 ? priv->block_sz -
> > + MLXCPLD_I2C_MAX_ADDR_LEN : priv->block_sz;
> > +
> > + if (msg->len < 0 || msg->len > max_len)
> > + return -EINVAL;
> > +
> > + *comm_len += msg->len;
> > + if (*comm_len > priv->block_sz)
> > + return -EINVAL;
> > + else
> > + return 0;
>
> Just 'return 0;' without else and indentation.
>
> > +}
> > +
> > +/* 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->pdev->dev, "Incorrect 0 num of messages\n");
> > + return -EINVAL;
> > + }
> > +
> > + if (unlikely(msgs[0].addr > 0x7f)) {
> > + dev_err(&priv->pdev->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->pdev->dev, "Invalid buf in msg[%d]\n",
> > + i);
> > + return -EINVAL;
> > + }
> > + if (unlikely(msgs[0].addr != msgs[i].addr)) {
> > + dev_err(&priv->pdev->dev, "Invalid addr in msg[%d]\n",
> > + i);
> > + return -EINVAL;
> > + }
> > + if (unlikely(mlxcpld_i2c_invalid_len(priv, &msgs[i],
> > + comm_len))) {
> > + dev_err(&priv->pdev->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) {
> > + if (comm_len == msgs[0].len) {
> > + /* Special case of addr_width = 0 */
> > + priv->xfer.addr_width = 0;
> > + priv->xfer.data_len = comm_len;
> > + } else {
> > + priv->xfer.addr_width = msgs[0].len;
> > + priv->xfer.data_len = comm_len - priv-
> >xfer.addr_width;
> > + }
> > + } else {
> > + /* Width (I2C_NUM_ADDR reg) isn't used in write command. */
> > + priv->xfer.addr_width = 0;
> > + priv->xfer.data_len = comm_len;
> > + }
>
> 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;
> }
>
> 7 lines vs. 12 lines.
>
> > +}
> > +
> > +/* 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);
>
> I would recomment to insert empty lines after mutex_lock() and before
> mutex_unlock().
>
> > +}
> > +
> > +/* 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(priv->poll_time/2, priv->poll_time);
>
> checkpatch complains:
>
> CHECK: spaces preferred around that '/' (ctx:VxV)
> #375: FILE: drivers/i2c/busses/i2c-mlxcpld.c:375:
> + usleep_range(priv->poll_time/2, priv->poll_time);
> ^
>
> > + timeout += priv->poll_time;
> > + } while (timeout < priv->xfer_to);
> > +
> > + if (timeout > priv->xfer_to)
>
> Corner case (timeout == priv->xfer_to) is missed here.
>
> if (timeout >= priv->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 = 1, timeout = 0;
> > + u8 datalen;
> > + int err = 0;
>
> Just "int err;"
>
> See "The Power of Undefined Values" https://rusty.ozlabs.org/?p=232
>
> > +
> > + do {
> > + usleep_range(priv->poll_time / 2, priv->poll_time);
> > + if (!mlxcpld_i2c_check_status(priv, &status))
> > + break;
> > + timeout += priv->poll_time;
> > + } while (status == 0 && timeout < priv->xfer_to);
> > +
> > + switch (status) {
> > + case MLXCPLD_LPCI2C_NO_IND:
> > + return -ETIMEDOUT;
>
> Please insert an empty line here to improve readability.
>
> > + case MLXCPLD_LPCI2C_ACK_IND:
> > + if (priv->xfer.cmd == I2C_M_RD) {
> > + /*
> > + * 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.
> > + */
> > + err = datalen = priv->xfer.data_len;
>
> checkpatch complains:
>
> CHECK: multiple assignments should be avoided
> #414: FILE: drivers/i2c/busses/i2c-mlxcpld.c:414:
> + err = datalen = priv->xfer.data_len;
>
> > + if (priv->xfer.msg_num == 1)
> > + i = 0;
> > +
> > + if (!priv->xfer.msg[i].buf)
> > + err = -EINVAL;
> > + else
> > + mlxcpld_i2c_read_comm(priv,
> > +
> MLXCPLD_LPCI2C_DATA_REG,
> > + priv->xfer.msg[i].buf,
> > + datalen);
> > + } else {
> > + err = priv->xfer.addr_width + priv->xfer.data_len;
> > + }
>
> Please try to avoid unnecessary indendation of big code chunks.
>
> For example here you may consider to rewrite it as:
>
> 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;
>
> datalen = priv->xfer.data_len;
>
> mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_DATA_REG,
> priv->xfer.msg[i].buf, datalen);
>
> return datalen;
>
> > + break;
> > + case MLXCPLD_LPCI2C_NACK_IND:
> > + err = -EAGAIN;
> > + break;
> > + case MLXCPLD_LPCI2C_ERR_IND:
> > + err = -EIO;
> > + break;
>
> This is dead code, from mlxcpld_i2c_check_status() function the 'status'
> variable can not be set to MLXCPLD_LPCI2C_ERR_IND.
>
> > + default:
> > + break;
> > + }
> > +
> > + return err;
> > +}
> > +
> > +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->pdev->dev, "Incorrect message\n");
> > + return err;
> > + }
> > +
> > + /* Check bus state */
> > + if (mlxcpld_i2c_wait_for_free(priv)) {
> > + dev_err(&priv->pdev->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->pdev->dev, "LPCI2C bridge is busy after
> reset\n");
> > + return -EIO;
> > + }
> > + }
> > +
> > + mlxcpld_i2c_set_transf_data(priv, msgs, num, comm_len);
> > +
> > + mutex_lock(&priv->lock);
>
> Please consider to insert an empty line here for readability.
>
> > + /* Do real transfer. Can't fail */
> > + mlxcpld_i2c_xfer_msg(priv);
>
> Please consider to insert an empty line here for readability.
>
> > + /* Wait for transaction complete */
> > + err = mlxcpld_i2c_wait_for_tc(priv);
>
> Please consider to insert an empty line here for readability.
>
> > + 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,
> > +};
> > +
> > +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);
>
> Please insert an empty line here for readability.
>
> > + priv->pdev = pdev;
>
> You store "pdev" in private data only to get &pdev->dev in runtime.
> Please store the pointer to that 'struct device' instead of pdev.
>
> > + priv->xfer_to = MLXCPLD_I2C_XFER_TO;
> > + priv->retr_num = MLXCPLD_I2C_RETR_NUM;
> > + priv->block_sz = MLXCPLD_I2C_DATA_REG_SZ;
> > + priv->poll_time = MLXCPLD_I2C_POLL_TIME;
>
> Why do you set all these constant values in runtime?
> Please consider to replace all of them in the code by the correspondent macros.
>
> And also please insert an empty line here for readability.
>
> > + /* Register with i2c layer */
> > + priv->adap = mlxcpld_i2c_adapter;
> > + priv->adap.dev.parent = &pdev->dev;
> > + i2c_set_adapdata(&priv->adap, priv);
> > + priv->adap.retries = priv->retr_num;
>
> priv->adap.retries = MLXCPLD_I2C_RETR_NUM;
>
> > + priv->adap.nr = MLXCPLD_I2C_BUS_NUM;
> > + priv->adap.timeout = usecs_to_jiffies(priv->xfer_to);
> > +
> > + err = i2c_add_numbered_adapter(&priv->adap);
>
> Why do you use i2c_add_numbered_adapter() instead of i2c_add_adapter() ?
Here it is set to 1. On our systems we used to have i2c0 for Intel i801, and i2c1 for our CPLD LPC/I2C logic.
After all I I want to enforce adapter id to pdev->id.
I already submitted code in module mlx-paltform.c for next, which will set this id and will probe this driver. Since all this code new and going to different branches it is not so trivial to sync different modules.
>
> > + if (err) {
> > + dev_err(&pdev->dev, "Failed to add %s adapter (%d)\n",
> > + MLXCPLD_I2C_DEVICE_NAME, err);
> > + goto fail_adapter;
> > + }
> > +
> > + priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADRR;
>
> Consider to move this assignment up to the rest of them.
>
> > + return 0;
> > +
> > +fail_adapter:
> > + 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)");
>
> Consider to enclose email into <>, see RFC822.
>
> > +MODULE_DESCRIPTION("Mellanox I2C-CPLD controller driver");
> > +MODULE_LICENSE("GPL v2");
>
> MODULE_LICENSE("Dual BSD/GPL");
>
> Correct?
>
> > +MODULE_ALIAS("platform:i2c-mlxcpld");
> >
>
> --
> With best wishes,
> Vladimir
^ permalink raw reply
* Xmas Offer
From: Mrs Julie Leach @ 2016-11-02 22:56 UTC (permalink / raw)
To: Recipients
You are a recipient to Mrs Julie Leach Donation of $3 million USD. Contact ( julieleach93@gmail.com) for claims.
^ permalink raw reply
* Re: [patch v2] i2c: add master driver for mellanox systems
From: Vladimir Zapolskiy @ 2016-11-02 23:08 UTC (permalink / raw)
To: vadimp, wsa; +Cc: linux-i2c, linux-kernel, jiri, Michael Shych
In-Reply-To: <1474297231-36264-1-git-send-email-vadimp@mellanox.com>
Hi Vadim,
On 19.09.2016 18:00, 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>
> v1->v2
> Fixes added by Vadim:
> - Put new record in Makefile in alphabetic order;
> - Remove http://www.mellanox.com from MAINTAINERS record;
the change does not apply cleanly on top of the recent source,
please consider to rebase it.
Anyway please consider to check some review points found below.
> ---
> Documentation/i2c/busses/i2c-mlxcpld | 47 +++
> MAINTAINERS | 8 +
> drivers/i2c/busses/Kconfig | 12 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-mlxcpld.c | 597 +++++++++++++++++++++++++++++++++++
> 5 files changed, 665 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 6781a3f..dc31231 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7667,6 +7667,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
> +
> SOFT-ROCE DRIVER (rxe)
> M: Moni Shoua <monis@mellanox.com>
> L: linux-rdma@vger.kernel.org
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 5c3993b..1126142a 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -1203,4 +1203,16 @@ config I2C_OPAL
> This driver can also be built as a module. If so, the module will be
> called as i2c-opal.
>
> +config I2C_MLXCPLD
> + tristate "Mellanox I2C driver"
> + depends on X86_64
> + default y
> + help
Please use one tab symbol as indentation above.
Please add the section preserving the alphabetical order.
> + 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.
> +
> endmenu
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 37f2819..4df3578 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -118,5 +118,6 @@ obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o
> obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o
> obj-$(CONFIG_I2C_XGENE_SLIMPRO) += i2c-xgene-slimpro.o
> obj-$(CONFIG_SCx200_ACB) += scx200_acb.o
> +obj-$(CONFIG_I2C_MLXCPLD) += i2c-mlxcpld.o
Please add the line preserving the alphabetical order.
>
> ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
> diff --git a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c
> new file mode 100644
> index 0000000..dd62190
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-mlxcpld.c
> @@ -0,0 +1,597 @@
> +/*
> + * 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.
So the code is dual licensed under BSD 3-Clause / GPLv2.
> + */
> +
> +#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_ADRR 0x2000
s/ADRR/ADDR/
> +#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 /* msec */
> +#define MLXCPLD_I2C_POLL_TIME 2000 /* msec */
> +
> +/* 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
This macro is not used.
> +#define MLXCPLD_LPCI2C_LPF_DFLT 0x2
This macro is not used.
> +#define MLXCPLD_LPCI2C_HALF_CYC_100 0x1f
This macro is not used.
> +#define MLXCPLD_LPCI2C_I2C_HOLD_100 0x3c
This macro is not used.
> +#define MLXCPLD_LPCI2C_TRANS_END 0x1
> +#define MLXCPLD_LPCI2C_STATUS_NACK 0x10
This macro is not used.
> +#define MLXCPLD_LPCI2C_ERR_IND -1
This macro is not used.
> +#define MLXCPLD_LPCI2C_NO_IND 0
> +#define MLXCPLD_LPCI2C_ACK_IND 1
> +#define MLXCPLD_LPCI2C_NACK_IND 2
> +
> +/**
> + * mlxcpld_i2c_regs - controller registers:
> + * @half_cyc - half cycle register
> + * @i2c_hold - hold register
> + * @config - config register
> + * @cmd - command register
> + * @cmd - status register
> + * @data - register data
> +**/
checkpatch warning:
WARNING: Block comments should align the * on each line
#85: FILE: drivers/i2c/busses/i2c-mlxcpld.c:85:
+ * @data - register data
+**/
Also for improving readability please add an empty line before
the following struct declaration.
> +struct mlxcpld_i2c_regs {
> + u8 half_cyc;
> + u8 i2c_hold;
> + u8 config;
> + u8 cmd;
> + u8 status;
> + u8 data[MLXCPLD_I2C_DATA_REG_SZ];
> +};
> +
This structure is not used, please remove it.
> +/**
> + * 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
> +**/
checkpatch warning:
WARNING: Block comments should align the * on each line
#103: FILE: drivers/i2c/busses/i2c-mlxcpld.c:103:
+ * @msg - pointer to message buffer
+**/
Also for improving readability please add an empty line before
the following struct declaration.
> +struct mlxcpld_i2c_curr_transf {
> + u8 cmd;
> + u8 addr_width;
> + u8 data_len;
> + u8 msg_num;
> + struct i2c_msg *msg;
> +};
> +
> +/**
> + * mlxcpld_i2c_priv - private controller data:
> + * @lpc_gen_dec_reg - register space
> + * @adap - i2c adapter
> + * @dev_id - device id
> + * @base_addr - base IO address
> + * @poll_time - polling time
> + * @xfer_to - transfer timeout in microsec (500000)
> + * @retr_num - access retries number (2)
> + * @block_sz - maximum data block size (36),
> + * @lock - bus access lock
> + * @lpc_i2c_res - lpc i2c resourse
> + * @lpc_cpld_res - lpc cpld resource
> + * @xfer - current i2c transfer block
> + * @pdev - platform device
> +**/
WARNING: Block comments should align the * on each line
#127: FILE: drivers/i2c/busses/i2c-mlxcpld.c:127:
+ * @pdev - platform device
+**/
Also for improving readability please add an empty line before
the following struct declaration.
> +struct mlxcpld_i2c_priv {
> + struct i2c_adapter adap;
> + u16 dev_id;
> + u16 base_addr;
> + u16 poll_time;
> + int xfer_to;
> + int retr_num;
Remove 'retr_num', set MLXCPLD_I2C_RETR_NUM directly.
Alsoe see my comment for .probe() function.
> + int block_sz;
> + struct mutex lock;
> + struct mlxcpld_i2c_curr_transf xfer;
> + struct platform_device *pdev;
> +};
checkpatch complains:
CHECK: Please use a blank line after function/struct/union/enum declarations
#140: FILE: drivers/i2c/busses/i2c-mlxcpld.c:140:
+};
+struct platform_device *mlxcpld_i2c_plat_dev;
> +struct platform_device *mlxcpld_i2c_plat_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 ? priv->block_sz -
> + MLXCPLD_I2C_MAX_ADDR_LEN : priv->block_sz;
> +
> + if (msg->len < 0 || msg->len > max_len)
> + return -EINVAL;
> +
> + *comm_len += msg->len;
> + if (*comm_len > priv->block_sz)
> + return -EINVAL;
> + else
> + return 0;
Just 'return 0;' without else and indentation.
> +}
> +
> +/* 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->pdev->dev, "Incorrect 0 num of messages\n");
> + return -EINVAL;
> + }
> +
> + if (unlikely(msgs[0].addr > 0x7f)) {
> + dev_err(&priv->pdev->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->pdev->dev, "Invalid buf in msg[%d]\n",
> + i);
> + return -EINVAL;
> + }
> + if (unlikely(msgs[0].addr != msgs[i].addr)) {
> + dev_err(&priv->pdev->dev, "Invalid addr in msg[%d]\n",
> + i);
> + return -EINVAL;
> + }
> + if (unlikely(mlxcpld_i2c_invalid_len(priv, &msgs[i],
> + comm_len))) {
> + dev_err(&priv->pdev->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) {
> + if (comm_len == msgs[0].len) {
> + /* Special case of addr_width = 0 */
> + priv->xfer.addr_width = 0;
> + priv->xfer.data_len = comm_len;
> + } else {
> + priv->xfer.addr_width = msgs[0].len;
> + priv->xfer.data_len = comm_len - priv->xfer.addr_width;
> + }
> + } else {
> + /* Width (I2C_NUM_ADDR reg) isn't used in write command. */
> + priv->xfer.addr_width = 0;
> + priv->xfer.data_len = comm_len;
> + }
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;
}
7 lines vs. 12 lines.
> +}
> +
> +/* 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);
I would recomment to insert empty lines after mutex_lock() and
before mutex_unlock().
> +}
> +
> +/* 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(priv->poll_time/2, priv->poll_time);
checkpatch complains:
CHECK: spaces preferred around that '/' (ctx:VxV)
#375: FILE: drivers/i2c/busses/i2c-mlxcpld.c:375:
+ usleep_range(priv->poll_time/2, priv->poll_time);
^
> + timeout += priv->poll_time;
> + } while (timeout < priv->xfer_to);
> +
> + if (timeout > priv->xfer_to)
Corner case (timeout == priv->xfer_to) is missed here.
if (timeout >= priv->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 = 1, timeout = 0;
> + u8 datalen;
> + int err = 0;
Just "int err;"
See "The Power of Undefined Values" https://rusty.ozlabs.org/?p=232
> +
> + do {
> + usleep_range(priv->poll_time / 2, priv->poll_time);
> + if (!mlxcpld_i2c_check_status(priv, &status))
> + break;
> + timeout += priv->poll_time;
> + } while (status == 0 && timeout < priv->xfer_to);
> +
> + switch (status) {
> + case MLXCPLD_LPCI2C_NO_IND:
> + return -ETIMEDOUT;
Please insert an empty line here to improve readability.
> + case MLXCPLD_LPCI2C_ACK_IND:
> + if (priv->xfer.cmd == I2C_M_RD) {
> + /*
> + * 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.
> + */
> + err = datalen = priv->xfer.data_len;
checkpatch complains:
CHECK: multiple assignments should be avoided
#414: FILE: drivers/i2c/busses/i2c-mlxcpld.c:414:
+ err = datalen = priv->xfer.data_len;
> + if (priv->xfer.msg_num == 1)
> + i = 0;
> +
> + if (!priv->xfer.msg[i].buf)
> + err = -EINVAL;
> + else
> + mlxcpld_i2c_read_comm(priv,
> + MLXCPLD_LPCI2C_DATA_REG,
> + priv->xfer.msg[i].buf,
> + datalen);
> + } else {
> + err = priv->xfer.addr_width + priv->xfer.data_len;
> + }
Please try to avoid unnecessary indendation of big code chunks.
For example here you may consider to rewrite it as:
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;
datalen = priv->xfer.data_len;
mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_DATA_REG,
priv->xfer.msg[i].buf, datalen);
return datalen;
> + break;
> + case MLXCPLD_LPCI2C_NACK_IND:
> + err = -EAGAIN;
> + break;
> + case MLXCPLD_LPCI2C_ERR_IND:
> + err = -EIO;
> + break;
This is dead code, from mlxcpld_i2c_check_status() function the 'status'
variable can not be set to MLXCPLD_LPCI2C_ERR_IND.
> + default:
> + break;
> + }
> +
> + return err;
> +}
> +
> +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->pdev->dev, "Incorrect message\n");
> + return err;
> + }
> +
> + /* Check bus state */
> + if (mlxcpld_i2c_wait_for_free(priv)) {
> + dev_err(&priv->pdev->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->pdev->dev, "LPCI2C bridge is busy after reset\n");
> + return -EIO;
> + }
> + }
> +
> + mlxcpld_i2c_set_transf_data(priv, msgs, num, comm_len);
> +
> + mutex_lock(&priv->lock);
Please consider to insert an empty line here for readability.
> + /* Do real transfer. Can't fail */
> + mlxcpld_i2c_xfer_msg(priv);
Please consider to insert an empty line here for readability.
> + /* Wait for transaction complete */
> + err = mlxcpld_i2c_wait_for_tc(priv);
Please consider to insert an empty line here for readability.
> + 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,
> +};
> +
> +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);
Please insert an empty line here for readability.
> + priv->pdev = pdev;
You store "pdev" in private data only to get &pdev->dev in runtime.
Please store the pointer to that 'struct device' instead of pdev.
> + priv->xfer_to = MLXCPLD_I2C_XFER_TO;
> + priv->retr_num = MLXCPLD_I2C_RETR_NUM;
> + priv->block_sz = MLXCPLD_I2C_DATA_REG_SZ;
> + priv->poll_time = MLXCPLD_I2C_POLL_TIME;
Why do you set all these constant values in runtime?
Please consider to replace all of them in the code by the
correspondent macros.
And also please insert an empty line here for readability.
> + /* Register with i2c layer */
> + priv->adap = mlxcpld_i2c_adapter;
> + priv->adap.dev.parent = &pdev->dev;
> + i2c_set_adapdata(&priv->adap, priv);
> + priv->adap.retries = priv->retr_num;
priv->adap.retries = MLXCPLD_I2C_RETR_NUM;
> + priv->adap.nr = MLXCPLD_I2C_BUS_NUM;
> + priv->adap.timeout = usecs_to_jiffies(priv->xfer_to);
> +
> + err = i2c_add_numbered_adapter(&priv->adap);
Why do you use i2c_add_numbered_adapter() instead of i2c_add_adapter() ?
> + if (err) {
> + dev_err(&pdev->dev, "Failed to add %s adapter (%d)\n",
> + MLXCPLD_I2C_DEVICE_NAME, err);
> + goto fail_adapter;
> + }
> +
> + priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADRR;
Consider to move this assignment up to the rest of them.
> + return 0;
> +
> +fail_adapter:
> + 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)");
Consider to enclose email into <>, see RFC822.
> +MODULE_DESCRIPTION("Mellanox I2C-CPLD controller driver");
> +MODULE_LICENSE("GPL v2");
MODULE_LICENSE("Dual BSD/GPL");
Correct?
> +MODULE_ALIAS("platform:i2c-mlxcpld");
>
--
With best wishes,
Vladimir
^ permalink raw reply
* RE: [patch v2] i2c: add master driver for mellanox systems
From: Vadim Pasternak @ 2016-11-02 11:28 UTC (permalink / raw)
To: wsa@the-dreams.de
Cc: linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org,
jiri@resnulli.us, Michael Shych
In-Reply-To: <AM5PR0501MB20970D81B8678603EE431A30A2CF0@AM5PR0501MB2097.eurprd05.prod.outlook.com>
Hi,
Sorry for reminding regarding the patch.
Should I modify it? Re-submit?
Thank you very much,
Vadim.
> -----Original Message-----
> From: Vadim Pasternak
> Sent: Wednesday, September 28, 2016 2:51 PM
> To: Vadim Pasternak <vadimp@mellanox.com>; wsa@the-dreams.de
> Cc: linux-i2c@vger.kernel.org; linux-kernel@vger.kernel.org; jiri@resnulli.us;
> Michael Shych <michaelsh@mellanox.com>
> Subject: RE: [patch v2] i2c: add master driver for mellanox systems
>
> Hi,
>
> I'd like to kindly clarify whether the patch I sent could be applied, or I need to
> modify something?
>
> Best regards,
> Vadim.
>
> > -----Original Message-----
> > From: vadimp@mellanox.com [mailto:vadimp@mellanox.com]
> > Sent: Monday, September 19, 2016 6:01 PM
> > To: wsa@the-dreams.de
> > Cc: linux-i2c@vger.kernel.org; linux-kernel@vger.kernel.org;
> > jiri@resnulli.us; Vadim Pasternak <vadimp@mellanox.com>; Michael Shych
> > <michaelsh@mellanox.com>
> > Subject: [patch v2] i2c: add master driver for mellanox systems
> >
> > 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>
> > 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 | 12 +
> > drivers/i2c/busses/Makefile | 1 +
> > drivers/i2c/busses/i2c-mlxcpld.c | 597
> > +++++++++++++++++++++++++++++++++++
> > 5 files changed, 665 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 6781a3f..dc31231 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -7667,6 +7667,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
> > +
> > SOFT-ROCE DRIVER (rxe)
> > M: Moni Shoua <monis@mellanox.com>
> > L: linux-rdma@vger.kernel.org
> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> > index 5c3993b..1126142a 100644
> > --- a/drivers/i2c/busses/Kconfig
> > +++ b/drivers/i2c/busses/Kconfig
> > @@ -1203,4 +1203,16 @@ config I2C_OPAL
> > This driver can also be built as a module. If so, the module will be
> > called as i2c-opal.
> >
> > +config I2C_MLXCPLD
> > + tristate "Mellanox I2C driver"
> > + depends on X86_64
> > + default y
> > + 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.
> > +
> > endmenu
> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> > index
> > 37f2819..4df3578 100644
> > --- a/drivers/i2c/busses/Makefile
> > +++ b/drivers/i2c/busses/Makefile
> > @@ -118,5 +118,6 @@ obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o
> > obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o
> > obj-$(CONFIG_I2C_XGENE_SLIMPRO) += i2c-xgene-slimpro.o
> > obj-$(CONFIG_SCx200_ACB) += scx200_acb.o
> > +obj-$(CONFIG_I2C_MLXCPLD) += i2c-mlxcpld.o
> >
> > ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG diff --git
> > a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c
> > new file mode 100644
> > index 0000000..dd62190
> > --- /dev/null
> > +++ b/drivers/i2c/busses/i2c-mlxcpld.c
> > @@ -0,0 +1,597 @@
> > +/*
> > + * 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_ADRR 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 /* msec */
> > +#define MLXCPLD_I2C_POLL_TIME 2000 /* msec */
> > +
> > +/* 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_LPF_DFLT 0x2
> > +#define MLXCPLD_LPCI2C_HALF_CYC_100 0x1f
> > +#define MLXCPLD_LPCI2C_I2C_HOLD_100 0x3c
> > +#define MLXCPLD_LPCI2C_TRANS_END 0x1
> > +#define MLXCPLD_LPCI2C_STATUS_NACK 0x10
> > +#define MLXCPLD_LPCI2C_ERR_IND -1
> > +#define MLXCPLD_LPCI2C_NO_IND 0
> > +#define MLXCPLD_LPCI2C_ACK_IND 1
> > +#define MLXCPLD_LPCI2C_NACK_IND 2
> > +
> > +/**
> > + * mlxcpld_i2c_regs - controller registers:
> > + * @half_cyc - half cycle register
> > + * @i2c_hold - hold register
> > + * @config - config register
> > + * @cmd - command register
> > + * @cmd - status register
> > + * @data - register data
> > +**/
> > +struct mlxcpld_i2c_regs {
> > + u8 half_cyc;
> > + u8 i2c_hold;
> > + u8 config;
> > + u8 cmd;
> > + u8 status;
> > + u8 data[MLXCPLD_I2C_DATA_REG_SZ];
> > +};
> > +
> > +/**
> > + * 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;
> > +};
> > +
> > +/**
> > + * mlxcpld_i2c_priv - private controller data:
> > + * @lpc_gen_dec_reg - register space
> > + * @adap - i2c adapter
> > + * @dev_id - device id
> > + * @base_addr - base IO address
> > + * @poll_time - polling time
> > + * @xfer_to - transfer timeout in microsec (500000)
> > + * @retr_num - access retries number (2)
> > + * @block_sz - maximum data block size (36),
> > + * @lock - bus access lock
> > + * @lpc_i2c_res - lpc i2c resourse
> > + * @lpc_cpld_res - lpc cpld resource
> > + * @xfer - current i2c transfer block
> > + * @pdev - platform device
> > +**/
> > +struct mlxcpld_i2c_priv {
> > + struct i2c_adapter adap;
> > + u16 dev_id;
> > + u16 base_addr;
> > + u16 poll_time;
> > + int xfer_to;
> > + int retr_num;
> > + int block_sz;
> > + struct mutex lock;
> > + struct mlxcpld_i2c_curr_transf xfer;
> > + struct platform_device *pdev;
> > +};
> > +struct platform_device *mlxcpld_i2c_plat_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 ? priv->block_sz -
> > + MLXCPLD_I2C_MAX_ADDR_LEN : priv->block_sz;
> > +
> > + if (msg->len < 0 || msg->len > max_len)
> > + return -EINVAL;
> > +
> > + *comm_len += msg->len;
> > + if (*comm_len > priv->block_sz)
> > + return -EINVAL;
> > + else
> > + 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->pdev->dev, "Incorrect 0 num of messages\n");
> > + return -EINVAL;
> > + }
> > +
> > + if (unlikely(msgs[0].addr > 0x7f)) {
> > + dev_err(&priv->pdev->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->pdev->dev, "Invalid buf in msg[%d]\n",
> > + i);
> > + return -EINVAL;
> > + }
> > + if (unlikely(msgs[0].addr != msgs[i].addr)) {
> > + dev_err(&priv->pdev->dev, "Invalid addr in msg[%d]\n",
> > + i);
> > + return -EINVAL;
> > + }
> > + if (unlikely(mlxcpld_i2c_invalid_len(priv, &msgs[i],
> > + comm_len))) {
> > + dev_err(&priv->pdev->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) {
> > + if (comm_len == msgs[0].len) {
> > + /* Special case of addr_width = 0 */
> > + priv->xfer.addr_width = 0;
> > + priv->xfer.data_len = comm_len;
> > + } else {
> > + priv->xfer.addr_width = msgs[0].len;
> > + priv->xfer.data_len = comm_len - priv-
> > >xfer.addr_width;
> > + }
> > + } else {
> > + /* Width (I2C_NUM_ADDR reg) isn't used in write command. */
> > + 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(priv->poll_time/2, priv->poll_time);
> > + timeout += priv->poll_time;
> > + } while (timeout < priv->xfer_to);
> > +
> > + if (timeout > priv->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 = 1, timeout = 0;
> > + u8 datalen;
> > + int err = 0;
> > +
> > + do {
> > + usleep_range(priv->poll_time / 2, priv->poll_time);
> > + if (!mlxcpld_i2c_check_status(priv, &status))
> > + break;
> > + timeout += priv->poll_time;
> > + } while (status == 0 && timeout < priv->xfer_to);
> > +
> > + switch (status) {
> > + case MLXCPLD_LPCI2C_NO_IND:
> > + return -ETIMEDOUT;
> > + case MLXCPLD_LPCI2C_ACK_IND:
> > + if (priv->xfer.cmd == I2C_M_RD) {
> > + /*
> > + * 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.
> > + */
> > + err = datalen = priv->xfer.data_len;
> > + if (priv->xfer.msg_num == 1)
> > + i = 0;
> > +
> > + if (!priv->xfer.msg[i].buf)
> > + err = -EINVAL;
> > + else
> > + mlxcpld_i2c_read_comm(priv,
> > +
> > MLXCPLD_LPCI2C_DATA_REG,
> > + priv->xfer.msg[i].buf,
> > + datalen);
> > + } else {
> > + err = priv->xfer.addr_width + priv->xfer.data_len;
> > + }
> > + break;
> > + case MLXCPLD_LPCI2C_NACK_IND:
> > + err = -EAGAIN;
> > + break;
> > + case MLXCPLD_LPCI2C_ERR_IND:
> > + err = -EIO;
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + return err;
> > +}
> > +
> > +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->pdev->dev, "Incorrect message\n");
> > + return err;
> > + }
> > +
> > + /* Check bus state */
> > + if (mlxcpld_i2c_wait_for_free(priv)) {
> > + dev_err(&priv->pdev->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->pdev->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,
> > +};
> > +
> > +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->pdev = pdev;
> > + priv->xfer_to = MLXCPLD_I2C_XFER_TO;
> > + priv->retr_num = MLXCPLD_I2C_RETR_NUM;
> > + priv->block_sz = MLXCPLD_I2C_DATA_REG_SZ;
> > + priv->poll_time = MLXCPLD_I2C_POLL_TIME;
> > + /* Register with i2c layer */
> > + priv->adap = mlxcpld_i2c_adapter;
> > + priv->adap.dev.parent = &pdev->dev;
> > + i2c_set_adapdata(&priv->adap, priv);
> > + priv->adap.retries = priv->retr_num;
> > + priv->adap.nr = MLXCPLD_I2C_BUS_NUM;
> > + priv->adap.timeout = usecs_to_jiffies(priv->xfer_to);
> > +
> > + err = i2c_add_numbered_adapter(&priv->adap);
> > + if (err) {
> > + dev_err(&pdev->dev, "Failed to add %s adapter (%d)\n",
> > + MLXCPLD_I2C_DEVICE_NAME, err);
> > + goto fail_adapter;
> > + }
> > +
> > + priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADRR;
> > +
> > + return 0;
> > +
> > +fail_adapter:
> > + 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("GPL v2"); MODULE_ALIAS("platform:i2c-mlxcpld");
> > --
> > 2.1.4
^ permalink raw reply
* [PATCH v4 6/6] Add Advantech iManager Watchdog driver
From: Richard Vidal-Dorsch @ 2016-11-02 8:37 UTC (permalink / raw)
To: linus.walleij-QSEj5FYQhm4dnm+yROfE0A,
gnurou-Re5JQEeQqe8AvxtiuMwx3w, jdelvare-IBi9RG/b67k,
linux-0h96xk9xTtrk1uMJSBkQmQ, wsa-z923LK4zBo2bacvFa/9K2g,
lee.jones-QSEj5FYQhm4dnm+yROfE0A,
jingoohan1-Re5JQEeQqe8AvxtiuMwx3w, tomi.valkeinen-l0cyMroinI0,
wim-IQzOog9fTRqzQB+pC5nmwQ, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-gpio-u79uwXL29TY76Z2rM5mHXA,
linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA,
linux-watchdog-u79uwXL29TY76Z2rM5mHXA,
k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ
Cc: Richard Vidal-Dorsch, jo.sunga-ELdSlb/RfAS1Z/+hSey0Gg,
weilun.huang-ELdSlb/RfAS1Z/+hSey0Gg,
andrew.chou-ELdSlb/RfAS1Z/+hSey0Gg
In-Reply-To: <20161102083751.6335-1-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
drivers/watchdog/Kconfig | 11 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/imanager_wdt.c | 303 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 315 insertions(+)
create mode 100644 drivers/watchdog/imanager_wdt.c
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index fdd3228..d6859da 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -912,6 +912,17 @@ config WAFER_WDT
To compile this driver as a module, choose M here: the
module will be called wafer5823wdt.
+config IMANAGER_WDT
+ tristate "Advantech iManager Watchdog"
+ depends on MFD_IMANAGER
+ select WATCHDOG_CORE
+ help
+ Support for Advantech iManager watchdog on some Advantech
+ SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module
+ will be called imanager_wdt.
+
config I6300ESB_WDT
tristate "Intel 6300ESB Timer/Watchdog"
depends on PCI
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index caa9f4a..eb7fccf 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -117,6 +117,7 @@ endif
obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
obj-$(CONFIG_IT87_WDT) += it87_wdt.o
obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+obj-$(CONFIG_IMANAGER_WDT) += imanager_wdt.o
obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
diff --git a/drivers/watchdog/imanager_wdt.c b/drivers/watchdog/imanager_wdt.c
new file mode 100644
index 0000000..53b409e
--- /dev/null
+++ b/drivers/watchdog/imanager_wdt.c
@@ -0,0 +1,303 @@
+/*
+ * Advantech iManager Watchdog driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch-ELdSlb/RfAS1Z/+hSey0Gg@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 as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define WDT_DEFAULT_TIMEOUT 30 /* seconds */
+#define WDT_FREQ 10 /* Hz */
+
+struct imanager_wdt_data {
+ struct imanager_device_data *imgr;
+ struct watchdog_device wdt;
+ ulong last_updated;
+ uint timeout;
+};
+
+static uint timeout = WDT_DEFAULT_TIMEOUT;
+module_param(timeout, uint, 0444);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1 <= timeout <= 65534, default="
+ __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ".");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0444);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+enum wdt_ctrl {
+ START = 1, STOP, RESET, GET_TIMEOUT, SET_TIMEOUT, STOPBOOT = 8
+};
+
+enum imanager_wdt_event {
+ WDT_EVT_NONE,
+ WDT_EVT_DELAY,
+ WDT_EVT_PWRBTN,
+ WDT_EVT_NMI,
+ WDT_EVT_RESET,
+ WDT_EVT_WDPIN,
+ WDT_EVT_SCI,
+};
+
+struct event_delay {
+ u16 delay,
+ pwrbtn,
+ nmi,
+ reset,
+ wdpin,
+ sci,
+ dummy;
+} __attribute__((__packed__));
+
+static int imanager_wdt_ctrl(struct imanager_ec_data *ec, int ctrl,
+ int event_type, uint timeout)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(0, 0, ctrl, NULL)
+ };
+ u8 *fevent = &msg.u.data[0];
+ struct event_delay *event = (struct event_delay *)&msg.u.data[1];
+ int val;
+
+ if (ctrl == SET_TIMEOUT) {
+ memset(event, 0xff, sizeof(*event));
+ msg.wlen = sizeof(*event);
+ *fevent = 0;
+ val = (!timeout) ? 0xffff : cpu_to_be16(timeout * WDT_FREQ);
+
+ switch (event_type) {
+ case WDT_EVT_DELAY:
+ event->delay = val;
+ break;
+ case WDT_EVT_PWRBTN:
+ event->pwrbtn = val;
+ break;
+ case WDT_EVT_NMI:
+ event->nmi = val;
+ break;
+ case WDT_EVT_RESET:
+ event->reset = val;
+ break;
+ case WDT_EVT_WDPIN:
+ event->wdpin = val;
+ break;
+ case WDT_EVT_SCI:
+ event->sci = val;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return imanager_write(ec, EC_CMD_WDT_CTRL, &msg);
+}
+
+static inline int imanager_wdt_disable_all(struct imanager_wdt_data *data)
+{
+ struct imanager_ec_data *ec = &data->imgr->ec;
+
+ return (imanager_wdt_ctrl(ec, STOP, WDT_EVT_NONE, 0) ||
+ imanager_wdt_ctrl(ec, STOPBOOT, WDT_EVT_NONE, 0));
+}
+
+static int imanager_wdt_set(struct imanager_wdt_data *data, uint timeout)
+{
+ struct imanager_ec_data *ec = &data->imgr->ec;
+ int ret;
+
+ if (time_before(jiffies, data->last_updated + HZ + HZ / 2))
+ return 0;
+
+ if (data->timeout == timeout)
+ return 0;
+
+ ret = imanager_wdt_ctrl(ec, SET_TIMEOUT, WDT_EVT_PWRBTN, timeout);
+ if (ret < 0)
+ return ret;
+
+ data->timeout = timeout;
+ data->last_updated = jiffies;
+
+ return 0;
+}
+
+static int imanager_wdt_set_timeout(struct watchdog_device *wdt, uint timeout)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ struct imanager_device_data *imgr = data->imgr;
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_wdt_set(data, timeout);
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static uint imanager_wdt_get_timeleft(struct watchdog_device *wdt)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ uint timeleft = 0;
+ ulong time_diff = ((jiffies - data->last_updated) / HZ);
+
+ if (data->last_updated && (data->timeout > time_diff))
+ timeleft = data->timeout - time_diff;
+
+ return timeleft;
+}
+
+static int imanager_wdt_start(struct watchdog_device *wdt)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ struct imanager_device_data *imgr = data->imgr;
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_wdt_ctrl(&imgr->ec, START, WDT_EVT_NONE, 0);
+ data->last_updated = jiffies;
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static int imanager_wdt_stop(struct watchdog_device *wdt)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ struct imanager_device_data *imgr = data->imgr;
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_wdt_ctrl(&imgr->ec, STOP, WDT_EVT_NONE, 0);
+ data->last_updated = 0;
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static int imanager_wdt_ping(struct watchdog_device *wdt)
+{
+ struct imanager_wdt_data *data = watchdog_get_drvdata(wdt);
+ struct imanager_device_data *imgr = data->imgr;
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_wdt_ctrl(&imgr->ec, RESET, WDT_EVT_NONE, 0);
+ data->last_updated = jiffies;
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static const struct watchdog_info imanager_wdt_info = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "imanager-wdt",
+};
+
+static const struct watchdog_ops imanager_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = imanager_wdt_start,
+ .stop = imanager_wdt_stop,
+ .ping = imanager_wdt_ping,
+ .set_timeout = imanager_wdt_set_timeout,
+ .get_timeleft = imanager_wdt_get_timeleft,
+};
+
+static int imanager_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_wdt_data *data;
+ struct watchdog_device *wdt_dev;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+
+ wdt_dev = &data->wdt;
+ wdt_dev->info = &imanager_wdt_info;
+ wdt_dev->ops = &imanager_wdt_ops;
+ wdt_dev->timeout = WDT_DEFAULT_TIMEOUT;
+ wdt_dev->min_timeout = 1;
+ wdt_dev->max_timeout = 0xfffe;
+
+ watchdog_set_nowayout(wdt_dev, nowayout);
+ watchdog_set_drvdata(wdt_dev, data);
+
+ ret = watchdog_register_device(wdt_dev);
+ if (ret) {
+ dev_err(dev, "Could not register watchdog device\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, data);
+
+ imanager_wdt_disable_all(data);
+ imanager_wdt_set_timeout(wdt_dev, timeout);
+
+ dev_info(dev, "Driver loaded (timeout=%d seconds)\n", timeout);
+
+ return 0;
+}
+
+static int imanager_wdt_remove(struct platform_device *pdev)
+{
+ struct imanager_wdt_data *data = platform_get_drvdata(pdev);
+
+ if (!nowayout)
+ imanager_wdt_disable_all(data);
+
+ watchdog_unregister_device(&data->wdt);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static void imanager_wdt_shutdown(struct platform_device *pdev)
+{
+ struct imanager_device_data *imgr = dev_get_drvdata(pdev->dev.parent);
+
+ mutex_lock(&imgr->lock);
+ imanager_wdt_ctrl(&imgr->ec, STOP, WDT_EVT_NONE, 0);
+ mutex_unlock(&imgr->lock);
+}
+
+static struct platform_driver imanager_wdt_driver = {
+ .driver = {
+ .name = "imanager-wdt",
+ },
+ .probe = imanager_wdt_probe,
+ .remove = imanager_wdt_remove,
+ .shutdown = imanager_wdt_shutdown,
+};
+
+module_platform_driver(imanager_wdt_driver);
+
+MODULE_DESCRIPTION("Advantech iManager Watchdog Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-wdt");
--
2.10.1
--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" 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
* [PATCH v4 5/6] Add Advantech iManager Backlight driver
From: Richard Vidal-Dorsch @ 2016-11-02 8:37 UTC (permalink / raw)
To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou
In-Reply-To: <20161102083751.6335-1-richard.dorsch@gmail.com>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/video/backlight/Kconfig | 8 ++
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/imanager_bl.c | 210 ++++++++++++++++++++++++++++++++++
3 files changed, 219 insertions(+)
create mode 100644 drivers/video/backlight/imanager_bl.c
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5ffa4b4..2dac696 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -251,6 +251,14 @@ config BACKLIGHT_HP700
If you have an HP Jornada 700 series,
say Y to include backlight control driver.
+config BACKLIGHT_IMANAGER
+ tristate "Advantech iManager Backlight/Brightness"
+ depends on MFD_IMANAGER
+ help
+ This enables support for Advantech iManager Backlight and
+ Brightness control of some Advantech SOM, MIO, AIMB, and
+ PCM modules/boards.
+
config BACKLIGHT_CARILLO_RANCH
tristate "Intel Carillo Ranch Backlight Driver"
depends on LCD_CLASS_DEVICE && PCI && X86 && FB_LE80578
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 16ec534..713b406 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_BACKLIGHT_GENERIC) += generic_bl.o
obj-$(CONFIG_BACKLIGHT_GPIO) += gpio_backlight.o
obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o
obj-$(CONFIG_BACKLIGHT_HP700) += jornada720_bl.o
+obj-$(CONFIG_BACKLIGHT_IMANAGER) += imanager_bl.o
obj-$(CONFIG_BACKLIGHT_IPAQ_MICRO) += ipaq_micro_bl.o
obj-$(CONFIG_BACKLIGHT_LM3533) += lm3533_bl.o
obj-$(CONFIG_BACKLIGHT_LM3630A) += lm3630a_bl.o
diff --git a/drivers/video/backlight/imanager_bl.c b/drivers/video/backlight/imanager_bl.c
new file mode 100644
index 0000000..d8b4e3d
--- /dev/null
+++ b/drivers/video/backlight/imanager_bl.c
@@ -0,0 +1,210 @@
+/*
+ * Advantech iManager Backlight driver
+ * Partially derived from wm831x_bl
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/backlight.h>
+#include <linux/device.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mfd/imanager.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define BL_MAX_PWM 100
+
+enum backlight_units { BL_UNIT_1 = 0, BL_UNIT_2 };
+
+static bool polarity = PWM_POLARITY_NORMAL;
+module_param(polarity, bool, 0444);
+MODULE_PARM_DESC(polarity, "Select backlight polarity (inverted := 1)");
+
+static ushort unit = BL_UNIT_1;
+module_param(unit, ushort, 0444);
+MODULE_PARM_DESC(unit, "Select backlight control unit [0, 1] (defaults to 0)");
+
+struct imanager_backlight_data {
+ struct imanager_device_data *imgr;
+};
+
+struct brightness_level {
+ uint value : 7, /* Brightness Value - LSB [6..0] */
+ enable : 1; /* Brightness Enable - MSB [7] */
+};
+
+struct backlight_ctrl {
+ uint enable : 1, /* Backlight Control Enable - LSB [0] */
+ pwmpol : 1, /* PWM Polarity - bit [1] */
+ blpol : 1, /* Backlight Polarity - bit [2] */
+ dnc : 5; /* Don't care - bit [7..3] */
+};
+
+static int imanager_bl_enable(struct imanager_ec_data *ec, int unit)
+{
+ u8 val8;
+ struct brightness_level *ctrl = (struct brightness_level *)&val8;
+ u8 devid = ec->bl.attr[unit]->did;
+ u8 bl_unit = ec->bl.brightness[unit];
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, bl_unit, &val8, sizeof(val8));
+ if (ret < 0)
+ return ret;
+
+ ctrl->enable = 1;
+
+ return imanager_write_ram(ec, EC_RAM_ACPI, devid, &val8, sizeof(val8));
+}
+
+static int imanager_bl_set_polarity(struct imanager_ec_data *ec, uint polarity)
+{
+ u8 val8;
+ struct backlight_ctrl *ctrl = (struct backlight_ctrl *)&val8;
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_BACKLIGHT_CTRL,
+ &val8, sizeof(val8));
+ if (ret < 0)
+ return ret;
+
+ ctrl->blpol = polarity ? 1 : 0;
+
+ return imanager_write_ram(ec, EC_RAM_ACPI, EC_OFFSET_BACKLIGHT_CTRL,
+ &val8, sizeof(val8));
+}
+
+static int imanager_bl_get_brightness(struct backlight_device *bd)
+{
+ struct imanager_backlight_data *data = bl_get_data(bd);
+ struct imanager_device_data *imgr = data->imgr;
+ u8 devid = imgr->ec.bl.attr[unit]->did;
+ int pwm;
+
+ mutex_lock(&imgr->lock);
+
+ pwm = imanager_read8(&imgr->ec, EC_CMD_HWP_RD, devid);
+ if (pwm < 0) {
+ dev_warn(&bd->dev, "Failed while reading PWM\n");
+ pwm = 0;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return polarity ? BL_MAX_PWM - pwm : pwm;
+}
+
+static int imanager_bl_set_brightness(struct backlight_device *bd)
+{
+ struct imanager_backlight_data *data = bl_get_data(bd);
+ struct imanager_device_data *imgr = data->imgr;
+ u8 devid = imgr->ec.bl.attr[unit]->did;
+ u8 brightness = bd->props.brightness;
+ int ret;
+
+ if (bd->props.power != FB_BLANK_UNBLANK)
+ brightness = 0;
+
+ if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+ brightness = 0;
+
+ if (bd->props.state & BL_CORE_SUSPENDED)
+ brightness = 0;
+
+ /* invert brightness if polarity is set */
+ brightness = polarity ? BL_MAX_PWM - brightness : brightness;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_write8(&imgr->ec, EC_CMD_HWP_WR, devid, brightness);
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static const struct backlight_ops imanager_bl_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .get_brightness = imanager_bl_get_brightness,
+ .update_status = imanager_bl_set_brightness,
+};
+
+static int imanager_bl_init(struct device *dev,
+ struct imanager_backlight_data *data)
+{
+ struct backlight_device *bd;
+ struct backlight_properties props;
+ int ret;
+
+ memset(&props, 0, sizeof(props));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = BL_MAX_PWM;
+ bd = devm_backlight_device_register(dev, "imanager-backlight", dev,
+ data, &imanager_bl_ops, &props);
+
+ if (IS_ERR(bd)) {
+ dev_err(dev, "Unable to register backlight device\n");
+ return PTR_ERR(bd);
+ }
+
+ bd->props.brightness = imanager_bl_get_brightness(bd);
+ bd->props.max_brightness = BL_MAX_PWM;
+ bd->props.power = FB_BLANK_UNBLANK;
+
+ backlight_update_status(bd);
+
+ ret = imanager_bl_enable(&data->imgr->ec, unit);
+ if (ret < 0)
+ dev_warn(dev, "Could not enable backlight control\n");
+
+ ret = imanager_bl_set_polarity(&data->imgr->ec, polarity);
+ if (ret < 0)
+ dev_warn(dev, "Could not set backlight polarity\n");
+
+ return 0;
+}
+
+static int imanager_bl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_backlight_data *data;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+
+ ret = imanager_bl_init(dev, data);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static struct platform_driver imanager_backlight_driver = {
+ .driver = {
+ .name = "imanager-backlight",
+ },
+ .probe = imanager_bl_probe,
+};
+
+module_platform_driver(imanager_backlight_driver);
+
+MODULE_DESCRIPTION("Advantech iManager Backlight driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-backlight");
--
2.10.1
^ permalink raw reply related
* [PATCH v4 4/6] Add Advantech iManager I2C driver
From: Richard Vidal-Dorsch @ 2016-11-02 8:37 UTC (permalink / raw)
To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou
In-Reply-To: <20161102083751.6335-1-richard.dorsch@gmail.com>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-imanager.c | 461 ++++++++++++++++++++++++++++++++++++++
3 files changed, 472 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-imanager.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d252276..7d1ecb4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -42,6 +42,16 @@ config I2C_ALI15X3
This driver can also be built as a module. If so, the module
will be called i2c-ali15x3.
+config I2C_IMANAGER
+ tristate "Advantech iManager I2C Interface"
+ depends on MFD_IMANAGER
+ help
+ This enables support for Advantech iManager I2C of some
+ Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-imanager.
+
config I2C_AMD756
tristate "AMD 756/766/768/8111 and nVidia nForce"
depends on PCI
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 29764cc..d9ff210 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_HIX5HD2) += i2c-hix5hd2.o
obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o
+obj-$(CONFIG_I2C_IMANAGER) += i2c-imanager.o
obj-$(CONFIG_I2C_IMG) += i2c-img-scb.o
obj-$(CONFIG_I2C_IMX) += i2c-imx.o
obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
diff --git a/drivers/i2c/busses/i2c-imanager.c b/drivers/i2c/busses/i2c-imanager.c
new file mode 100644
index 0000000..15c496d
--- /dev/null
+++ b/drivers/i2c/busses/i2c-imanager.c
@@ -0,0 +1,461 @@
+/*
+ * Advantech iManager SMBus bus driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#define I2C_SMBUS_BLOCK_SIZE 32UL
+#define I2C_MAX_READ_SIZE I2C_SMBUS_BLOCK_SIZE
+#define I2C_MAX_WRITE_SIZE (I2C_SMBUS_BLOCK_SIZE - 1)
+
+#define EC_HWRAM_OFFSET_STATUS 0UL
+
+#define I2C_ERR_PROTO 0x19UL
+#define I2C_ERR_TIMEOUT 0x18UL
+#define I2C_ERR_ACCESS 0x17UL
+#define I2C_ERR_UNKNOWN 0x13UL
+#define I2C_ERR_ADDR_NACK 0x10UL
+
+#define SMBUS_FREQ_50KHZ 0x0100
+#define SMBUS_FREQ_100KHZ 0x0200
+#define SMBUS_FREQ_400KHZ 0x0300
+
+#define imanager_i2c_wr_combined(ec, message) \
+ imanager_i2c_block_wr_rw_combined(ec, message, EC_CMD_I2C_WR)
+
+#define imanager_i2c_rw_combined(ec, message) \
+ imanager_i2c_block_wr_rw_combined(ec, message, EC_CMD_I2C_RW)
+
+struct ec_i2c_status {
+ u32 error : 7;
+ u32 complete : 1;
+};
+
+struct adapter_info {
+ struct i2c_adapter adapter;
+ int smb_devid;
+};
+
+struct imanager_i2c_data {
+ struct device *dev;
+ struct imanager_device_data *imgr;
+ struct adapter_info adap_info[EC_MAX_SMB_NUM];
+ int nadap;
+};
+
+static int imanager_i2c_eval_status(u8 status)
+{
+ struct ec_i2c_status *_status = (struct ec_i2c_status *)&status;
+
+ switch (_status->error) {
+ case 0:
+ return 0;
+ case I2C_ERR_ADDR_NACK:
+ return -ENXIO;
+ case I2C_ERR_ACCESS:
+ return -EACCES;
+ case I2C_ERR_UNKNOWN:
+ return -EAGAIN;
+ case I2C_ERR_TIMEOUT:
+ return -ETIME;
+ case I2C_ERR_PROTO:
+ return -EPROTO;
+ }
+
+ return -EIO;
+}
+
+static int imanager_i2c_wait_proc_complete(struct imanager_ec_data *ec)
+{
+ int ret, i;
+ u8 val;
+
+ for (i = 0; i < EC_MAX_RETRY; i++) {
+ ret = imanager_read_ram(ec, EC_RAM_HW, EC_HWRAM_OFFSET_STATUS,
+ &val, sizeof(val));
+ if (ret < 0)
+ return ret;
+
+ if (!val)
+ return 0;
+
+ usleep_range(EC_DELAY_MIN, EC_DELAY_MAX);
+ }
+
+ return -ETIME;
+}
+
+static int imanager_i2c_block_wr_rw_combined(struct imanager_ec_data *ec,
+ struct imanager_ec_message *msg,
+ unsigned int protocol)
+{
+ int ret;
+
+ ret = imanager_i2c_wait_proc_complete(ec);
+ if (ret)
+ return ret;
+
+ ret = imanager_write(ec, protocol, msg);
+ if (ret)
+ return imanager_i2c_eval_status(ret);
+
+ if (msg->rlen) {
+ if (msg->rlen == 1)
+ return msg->u.data[0];
+ else if (msg->rlen == 2)
+ return (msg->u.data[1] << 8) | msg->u.data[0];
+ else
+ return msg->rlen;
+ }
+
+ return 0;
+}
+
+static inline int
+imanager_i2c_write_freq(struct imanager_ec_data *ec, int did, int freq)
+{
+ return imanager_write16(ec, EC_CMD_SMB_FREQ_WR, did, freq);
+}
+
+static inline int eval_read_len(int len)
+{
+ return len && len <= I2C_MAX_READ_SIZE ? len : I2C_MAX_READ_SIZE;
+}
+
+static inline int
+imanager_i2c_read_block(struct imanager_ec_data *ec,
+ struct imanager_ec_message *msg, u8 *buf)
+{
+ int ret;
+
+ ret = imanager_i2c_wr_combined(ec, msg);
+ if (ret < 0)
+ return ret;
+
+ buf[0] = ret;
+ memcpy(&buf[1], msg->u.data, ret);
+
+ return 0;
+}
+
+static inline int
+imanager_i2c_write_block(struct imanager_ec_data *ec,
+ struct imanager_ec_message *msg, u8 *buf)
+{
+ if (!buf[0] || (buf[0] > I2C_MAX_WRITE_SIZE))
+ return -EINVAL;
+
+ memcpy(&msg->u.data[EC_MSG_HDR_SIZE], &buf[1], buf[0]);
+
+ return imanager_i2c_wr_combined(ec, msg);
+}
+
+static s32 imanager_i2c_xfer(struct i2c_adapter *adap, u16 addr, ushort flags,
+ char read_write, u8 command, int size,
+ union i2c_smbus_data *smb_data)
+{
+ struct imanager_i2c_data *data = i2c_get_adapdata(adap);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct device *dev = data->dev;
+ int smb_devid = *(int *)adap->algo_data;
+ int val, ret = 0;
+ u16 addr16 = addr << 1; /* convert to 8-bit i2c slave address */
+ u8 *buf = smb_data->block;
+ struct imanager_ec_message msg = {
+ .rlen = 0,
+ .wlen = EC_MSG_HDR_SIZE,
+ .param = smb_devid,
+ .u = {
+ .smb.hdr = {
+ .addr_low = addr16 & 0x00ff,
+ .addr_high = addr16 >> 8,
+ .rlen = 0,
+ .wlen = 0,
+ },
+ },
+ };
+ struct imanager_ec_smb_message *smb = &msg.u.smb;
+
+ mutex_lock(&imgr->lock);
+
+ switch (size) {
+ case I2C_SMBUS_QUICK:
+ msg.rlen = 0;
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 1;
+ ret = imanager_i2c_wr_combined(ec, &msg);
+ break;
+ case I2C_SMBUS_BYTE:
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ smb->hdr.rlen = 1;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ if (val < 0)
+ ret = val;
+ } else {
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ msg.rlen = 1;
+ smb->hdr.rlen = 1;
+ smb->hdr.wlen = 0;
+ val = imanager_i2c_rw_combined(ec, &msg);
+ if (val < 0)
+ ret = val;
+ else
+ smb_data->byte = val;
+ break;
+ }
+ case I2C_SMBUS_BYTE_DATA:
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ msg.wlen += 1;
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 2;
+ smb->hdr.cmd = command;
+ smb->data[0] = smb_data->byte;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ } else {
+ msg.rlen = 1;
+ smb->hdr.rlen = 1;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ }
+ if (val < 0)
+ ret = val;
+ else
+ smb_data->byte = val;
+ break;
+ case I2C_SMBUS_WORD_DATA:
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ msg.wlen += 2;
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 3;
+ smb->hdr.cmd = command;
+ smb->data[0] = smb_data->word & 0x00ff;
+ smb->data[1] = smb_data->word >> 8;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ } else {
+ msg.rlen = 2;
+ smb->hdr.rlen = 2;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ val = imanager_i2c_wr_combined(ec, &msg);
+ }
+ if (val < 0)
+ ret = val;
+ else
+ smb_data->word = val;
+ break;
+ case I2C_SMBUS_BLOCK_DATA:
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ msg.wlen += buf[0];
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 1 + buf[0];
+ smb->hdr.cmd = command;
+ ret = imanager_i2c_write_block(ec, &msg, buf);
+ } else {
+ msg.rlen = eval_read_len(buf[0]);
+ smb->hdr.rlen = msg.rlen;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ ret = imanager_i2c_read_block(ec, &msg, buf);
+ }
+ break;
+ case I2C_SMBUS_I2C_BLOCK_DATA:
+ if (!smb_data) {
+ ret = -EINVAL;
+ break;
+ }
+ if (read_write == I2C_SMBUS_WRITE) {
+ msg.rlen = 1;
+ msg.wlen += buf[0];
+ smb->hdr.rlen = 0;
+ smb->hdr.wlen = 1 + buf[0];
+ smb->hdr.cmd = command;
+ ret = imanager_i2c_write_block(ec, &msg, buf);
+ } else {
+ msg.rlen = eval_read_len(buf[0]);
+ smb->hdr.rlen = msg.rlen;
+ smb->hdr.wlen = 1;
+ smb->hdr.cmd = command;
+ ret = imanager_i2c_read_block(ec, &msg, buf);
+ }
+ break;
+ default:
+ dev_err(dev, "Unsupported transaction %d\n", size);
+ ret = -EOPNOTSUPP;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static u32 imanager_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
+}
+
+static const struct i2c_algorithm imanager_i2c_algorithm = {
+ .smbus_xfer = imanager_i2c_xfer,
+ .functionality = imanager_i2c_func,
+};
+
+static const struct i2c_adapter imanager_i2c_adapters[] = {
+ [SMB_EEP] = {
+ .owner = THIS_MODULE,
+ .name = "iManager SMB EEP adapter",
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &imanager_i2c_algorithm,
+ },
+ [I2C_OEM] = {
+ .owner = THIS_MODULE,
+ .name = "iManager I2C OEM adapter",
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &imanager_i2c_algorithm,
+ },
+ [SMB_1] = {
+ .owner = THIS_MODULE,
+ .name = "iManager SMB 1 adapter",
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &imanager_i2c_algorithm,
+ },
+ [SMB_PECI] = {
+ .owner = THIS_MODULE,
+ .name = "iManager SMB PECI adapter",
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &imanager_i2c_algorithm,
+ },
+};
+
+static int
+imanager_i2c_add_bus(struct imanager_i2c_data *i2c, struct adapter_info *info,
+ const struct i2c_adapter *adap, int did, int freq)
+{
+ int ret;
+
+ info->adapter = *adap;
+ info->adapter.dev.parent = i2c->dev;
+ info->smb_devid = did;
+ info->adapter.algo_data = &info->smb_devid;
+ i2c_set_adapdata(&info->adapter, i2c);
+
+ ret = i2c_add_adapter(&info->adapter);
+ if (ret) {
+ dev_warn(i2c->dev, "Failed to add %s\n", info->adapter.name);
+ return ret;
+ }
+
+ ret = imanager_i2c_write_freq(&i2c->imgr->ec, did, freq);
+ if (ret < 0)
+ dev_warn(i2c->dev, "Failed to set bus frequency of %s\n",
+ info->adapter.name);
+
+ return 0;
+}
+
+static int imanager_i2c_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_device_attribute **attr = imgr->ec.i2c.attr;
+ struct imanager_i2c_data *data;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+ data->dev = dev;
+
+ if (attr[SMB_EEP])
+ imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+ &imanager_i2c_adapters[SMB_EEP],
+ attr[SMB_EEP]->did, SMBUS_FREQ_100KHZ);
+
+ if (attr[I2C_OEM])
+ imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+ &imanager_i2c_adapters[I2C_OEM],
+ attr[I2C_OEM]->did, SMBUS_FREQ_400KHZ);
+
+ if (attr[SMB_1])
+ imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+ &imanager_i2c_adapters[SMB_1],
+ attr[SMB_1]->did, SMBUS_FREQ_100KHZ);
+
+ if (attr[SMB_PECI])
+ imanager_i2c_add_bus(data, &data->adap_info[data->nadap++],
+ &imanager_i2c_adapters[SMB_PECI],
+ attr[SMB_PECI]->did, SMBUS_FREQ_100KHZ);
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static int imanager_i2c_remove(struct platform_device *pdev)
+{
+ struct imanager_i2c_data *i2c = dev_get_drvdata(&pdev->dev);
+ int i;
+
+ for (i = 0; i < i2c->nadap; i++) {
+ i2c_del_adapter(&i2c->adap_info[i].adapter);
+ i2c_set_adapdata(&i2c->adap_info[i].adapter, NULL);
+ }
+
+ return 0;
+}
+
+static struct platform_driver imanager_i2c_driver = {
+ .driver = {
+ .name = "imanager-smbus",
+ },
+ .probe = imanager_i2c_probe,
+ .remove = imanager_i2c_remove,
+};
+
+module_platform_driver(imanager_i2c_driver);
+
+MODULE_DESCRIPTION("Advantech iManager SMBus Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-smbus");
--
2.10.1
^ permalink raw reply related
* [PATCH v4 3/6] Add Advantech iManager HWmon driver
From: Richard Vidal-Dorsch @ 2016-11-02 8:37 UTC (permalink / raw)
To: linus.walleij-QSEj5FYQhm4dnm+yROfE0A,
gnurou-Re5JQEeQqe8AvxtiuMwx3w, jdelvare-IBi9RG/b67k,
linux-0h96xk9xTtrk1uMJSBkQmQ, wsa-z923LK4zBo2bacvFa/9K2g,
lee.jones-QSEj5FYQhm4dnm+yROfE0A,
jingoohan1-Re5JQEeQqe8AvxtiuMwx3w, tomi.valkeinen-l0cyMroinI0,
wim-IQzOog9fTRqzQB+pC5nmwQ, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-gpio-u79uwXL29TY76Z2rM5mHXA,
linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA,
linux-watchdog-u79uwXL29TY76Z2rM5mHXA,
k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ
Cc: Richard Vidal-Dorsch, jo.sunga-ELdSlb/RfAS1Z/+hSey0Gg,
weilun.huang-ELdSlb/RfAS1Z/+hSey0Gg,
andrew.chou-ELdSlb/RfAS1Z/+hSey0Gg
In-Reply-To: <20161102083751.6335-1-richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/imanager-hwmon.c | 1226 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 1238 insertions(+)
create mode 100644 drivers/hwmon/imanager-hwmon.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 45cef3d..7a9db12 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -625,6 +625,17 @@ config SENSORS_CORETEMP
sensor inside your CPU. Most of the family 6 CPUs
are supported. Check Documentation/hwmon/coretemp for details.
+config SENSORS_IMANAGER
+ tristate "Advantech iManager Hardware Monitoring"
+ depends on MFD_IMANAGER
+ select HWMON_VID
+ help
+ This enables support for Advantech iManager hardware monitoring
+ of some Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module
+ will be called imanager-hwmon.
+
config SENSORS_IT87
tristate "ITE IT87xx and compatibles"
depends on !PPC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index aecf4ba..3f489ae 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o
+obj-$(CONFIG_SENSORS_IMANAGER) += imanager-hwmon.o
obj-$(CONFIG_SENSORS_INA209) += ina209.o
obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
obj-$(CONFIG_SENSORS_INA3221) += ina3221.o
diff --git a/drivers/hwmon/imanager-hwmon.c b/drivers/hwmon/imanager-hwmon.c
new file mode 100644
index 0000000..af72c35
--- /dev/null
+++ b/drivers/hwmon/imanager-hwmon.c
@@ -0,0 +1,1226 @@
+/*
+ * Advantech iManager Hardware Monitoring driver
+ * Partially derived from nct6775
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch-ELdSlb/RfAS1Z/+hSey0Gg@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 as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon-vid.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+/* Voltage computation (10-bit ADC, 0..3V input) */
+#define SCALE_IN 2933 /* (3000mV / (2^10 - 1)) * 1000 */
+
+#define HWM_STATUS_UNDEFINED_ITEM 2UL
+#define HWM_STATUS_UNDEFINED_DID 3UL
+#define HWM_STATUS_UNDEFINED_HWPIN 4UL
+
+/* iManager EC FW pwm[1-*]_mode values are switched */
+enum imanager_pwm_mode { CTRL_PWM, CTRL_RPM };
+/* EC FW defines pwm_enable mode 'full speed' besides mode 'off' */
+enum imanager_pwm_enable { MODE_OFF, MODE_FULL, MODE_MANUAL, MODE_AUTO };
+
+/*
+ * iManager FAN device defs
+ */
+struct fan_dev_config {
+ u8 did,
+ hwpin,
+ tachoid,
+ status,
+ control,
+ temp_max,
+ temp_min,
+ temp_stop,
+ pwm_max,
+ pwm_min;
+ u16 rpm_max;
+ u16 rpm_min;
+ u8 debounce; /* debounce time, not used */
+ u8 temp; /* Current Thermal Zone Temperature */
+ u16 rpm_target; /* RPM Target Speed, not used */
+} __attribute__((__packed__));
+
+struct fan_alert_limit {
+ u16 min,
+ max;
+} __attribute__((__packed__));
+
+struct fan_status {
+ u32 sysctl : 1, /* System Control flag */
+ tacho : 1, /* FAN tacho source defined */
+ pulse : 1, /* FAN pulse type defined */
+ thermal : 1, /* Thermal zone init */
+ i2clink : 1, /* I2C protocol fail flag (thermal sensor) */
+ dnc : 1, /* don't care */
+ mode : 2; /* FAN Control mode */
+};
+
+/**
+ * FAN Control bit field
+ * enable: 0:Disabled, 1:Enabled
+ * type: 0:PWM, 1:RPM
+ * pulse: 0:Undefined 1:2 Pulses 2:4 Pulses
+ * tacho: 1:CPU FAN, 2:SYS FAN1, 3:SYS FAN2
+ * mode: 0:Off, 1:Full, 2:Manual, 3:Auto
+ */
+struct fan_ctrl {
+ u32 enable : 1, /* SmartFAN control on/off */
+ type : 1, /* FAN control type [0, 1] PWM/RPM */
+ pulse : 2, /* FAN pulse [0..2] */
+ tacho : 2, /* FAN Tacho Input [1..3] */
+ mode : 2; /* off/full/manual/auto */
+};
+
+/* Default Voltage Sensors */
+struct imanager_hwmon_adc {
+ bool valid; /* if set, below values are valid */
+ unsigned int value;
+ unsigned int min;
+ unsigned int max;
+ unsigned int average;
+ unsigned int lowest;
+ unsigned int highest;
+};
+
+struct imanager_hwmon_smartfan {
+ bool valid; /* if set, below values are valid */
+ unsigned int pwm;
+ unsigned int speed;
+ bool speed_min_alarm;
+ bool speed_max_alarm;
+ struct fan_dev_config cfg;
+};
+
+struct imanager_hwmon_data {
+ struct imanager_device_data *imgr;
+ const struct attribute_group *groups[3];
+ unsigned long samples;
+ unsigned long last_updated;
+ struct imanager_hwmon_adc adc[EC_MAX_ADC_NUM];
+ struct imanager_hwmon_smartfan fan[EC_MAX_FAN_NUM];
+};
+
+static int imanager_hwmon_read_fan_config(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(EC_F_HWMON_MSG, 0, num, (u8 *)&fan->cfg)
+ };
+ int ret;
+
+ ret = imanager_read(ec, EC_CMD_FAN_CTL_RD, &msg);
+ if (ret)
+ return ret;
+
+ return fan->cfg.did ? 0 : -ENODEV;
+}
+
+static int
+imanager_hwmon_write_fan_config(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_ec_message msg = {
+ IMANAGER_MSG_SIMPLE(0, sizeof(fan->cfg), num, (u8 *)&fan->cfg)
+ };
+ int err;
+
+ err = imanager_write(ec, EC_CMD_FAN_CTL_WR, &msg);
+ if (err < 0) {
+ return err;
+ } else if (err) {
+ switch (err) {
+ case HWM_STATUS_UNDEFINED_ITEM:
+ case HWM_STATUS_UNDEFINED_DID:
+ case HWM_STATUS_UNDEFINED_HWPIN:
+ return -ENODEV;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * FAN max/min alert bits are stored as bit pairs in a 8-bit register.
+ * FAN0~2: 2:{max2, min2}, 1:{max1, min1}, 0:{max0, min0}
+ */
+#define IS_FAN_ALERT_MIN(fan_num, var) test_bit(BIT((fan_num) << 1), var)
+#define IS_FAN_ALERT_MAX(fan_num, var) test_bit(BIT(((fan_num) << 1) + 1), var)
+
+static int imanager_hwmon_read_fan_alert(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ const ulong alert_flags = 0;
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT,
+ (u8 *)&alert_flags, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ fan->speed_min_alarm = IS_FAN_ALERT_MIN(num, &alert_flags);
+ fan->speed_max_alarm = IS_FAN_ALERT_MAX(num, &alert_flags);
+
+ return 0;
+}
+
+static int imanager_hwmon_write_fan_alert(struct imanager_ec_data *ec, int fnum,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct fan_alert_limit limits[EC_MAX_FAN_NUM];
+ struct fan_alert_limit *limit = &limits[fnum];
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+ (u8 *)limits, sizeof(limits));
+ if (ret < 0)
+ return ret;
+
+ limit->min = fan->cfg.rpm_min;
+ limit->max = fan->cfg.rpm_max;
+
+ return imanager_write_ram(ec, EC_RAM_ACPI, EC_OFFSET_FAN_ALERT_LIMIT,
+ (u8 *)limits, sizeof(limits));
+}
+
+static int imanager_hwmon_read_adc(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_adc *adc)
+{
+ struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+ int ret;
+
+ adc->valid = false;
+
+ ret = imanager_read16(ec, EC_CMD_HWP_RD, attr->did);
+ if (ret < 0)
+ return ret;
+
+ adc->value = ret * attr->ecdev->scale;
+ adc->valid = true;
+
+ return 0;
+}
+
+static int imanager_hwmon_read_fan_ctrl(struct imanager_ec_data *ec, int num,
+ struct imanager_hwmon_smartfan *fan)
+{
+ struct imanager_device_attribute *attr = ec->hwmon.adc.attr[num];
+
+ int ret;
+
+ fan->valid = false;
+
+ ret = imanager_hwmon_read_fan_config(ec, num, fan);
+ if (ret < 0)
+ return ret;
+
+ ret = imanager_read16(ec, EC_CMD_HWP_RD, fan->cfg.tachoid);
+ if (ret < 0)
+ return ret;
+
+ fan->speed = ret;
+
+ ret = imanager_read8(ec, EC_CMD_HWP_RD, attr->did);
+ if (ret < 0)
+ return ret;
+
+ fan->pwm = ret;
+
+ ret = imanager_hwmon_read_fan_alert(ec, num, fan);
+ if (ret)
+ return ret;
+
+ fan->valid = true;
+
+ return 0;
+}
+
+static inline uint in_from_reg(u16 val)
+{
+ return clamp_val(DIV_ROUND_CLOSEST(val * SCALE_IN, 1000), 0, 65535);
+}
+
+static inline u16 in_to_reg(uint val)
+{
+ return clamp_val(DIV_ROUND_CLOSEST(val * 1000, SCALE_IN), 0, 65535);
+}
+
+static struct imanager_hwmon_data *
+imanager_hwmon_update_device(struct device *dev)
+{
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct imanager_hwmon_device *hwmon = &ec->hwmon;
+ int i;
+
+ mutex_lock(&imgr->lock);
+
+ if (time_after(jiffies, data->last_updated + HZ + HZ / 2)) {
+ /* Measured voltages */
+ for (i = 0; i < hwmon->adc.num; i++)
+ imanager_hwmon_read_adc(ec, i, &data->adc[i]);
+
+ /* Measured fan speeds */
+ for (i = 0; i < hwmon->fan.num; i++)
+ imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+ data->last_updated = jiffies;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return data;
+}
+
+static ssize_t
+show_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].value));
+}
+
+static ssize_t
+show_in_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].min));
+}
+
+static ssize_t
+show_in_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%u\n", in_from_reg(data->adc[nr].max));
+}
+
+static ssize_t
+store_in_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ data->adc[nr].min = in_to_reg(val);
+
+ return count;
+}
+
+static ssize_t
+store_in_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ data->adc[nr].max = in_to_reg(val);
+
+ return count;
+}
+
+static ssize_t
+show_in_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+ int val = 0;
+
+ if (adc->valid)
+ val = (adc->value < adc->min) || (adc->value > adc->max);
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+show_in_average(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (adc->average) {
+ adc->average = DIV_ROUND_CLOSEST(adc->average * data->samples +
+ adc->value, ++data->samples);
+ } else {
+ adc->average = adc->value;
+ data->samples = 1;
+ }
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->average));
+}
+
+static ssize_t
+show_in_lowest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (!adc->lowest || (adc->value < adc->lowest))
+ adc->lowest = adc->value;
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->lowest));
+}
+
+static ssize_t
+show_in_highest(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ struct imanager_hwmon_adc *adc = &data->adc[nr];
+
+ if (!adc->highest || (adc->value > adc->highest))
+ adc->highest = adc->value;
+
+ return sprintf(buf, "%u\n", in_from_reg(adc->highest));
+}
+
+static ssize_t
+store_in_reset_history(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index;
+ unsigned long reset;
+ int err;
+
+ err = kstrtoul(buf, 10, &reset);
+ if (err < 0)
+ return err;
+
+ if (reset) {
+ data->adc[nr].lowest = 0;
+ data->adc[nr].highest = 0;
+ data->adc[nr].average = 0;
+ data->samples = 0;
+ }
+
+ return count;
+}
+
+static ssize_t
+show_temp(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.temp * 1000);
+}
+
+static ssize_t
+show_fan_in(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+
+ return sprintf(buf, "%u\n", fan->valid ? fan->speed : 0);
+}
+
+static inline int check_fan_alarm(const struct imanager_hwmon_smartfan *fan)
+{
+ int rpm_min = cpu_to_be16(fan->cfg.rpm_min);
+ int rpm_max = cpu_to_be16(fan->cfg.rpm_max);
+
+ return !fan->valid || fan->speed_min_alarm || fan->speed_max_alarm ||
+ (!fan->speed && fan->pwm) || (fan->speed < rpm_min) ||
+ (fan->speed > rpm_max);
+}
+
+static ssize_t
+show_fan_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ bool is_alarm = (ctrl->mode == MODE_AUTO) ? 0 : check_fan_alarm(fan);
+
+ return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t
+show_fan_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ int rpm_min = cpu_to_be16(data->fan[nr].cfg.rpm_min);
+
+ return sprintf(buf, "%u\n", rpm_min);
+}
+
+static ssize_t
+show_fan_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ int rpm_max = cpu_to_be16(data->fan[nr].cfg.rpm_max);
+
+ return sprintf(buf, "%u\n", rpm_max);
+}
+
+static ssize_t
+store_fan_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.rpm_min = cpu_to_be16(val);
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_fan_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.rpm_max = cpu_to_be16(val);
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ u32 val = DIV_ROUND_CLOSEST(data->fan[nr].pwm * 255, 100);
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+store_pwm(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ int did = imgr->ec.hwmon.fan.attr[nr]->did;
+ unsigned long pwm = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &pwm);
+ if (err < 0)
+ return err;
+
+ pwm = DIV_ROUND_CLOSEST(pwm * 100, 255);
+
+ mutex_lock(&imgr->lock);
+
+ if (ctrl->mode == MODE_MANUAL) {
+ ctrl->type = CTRL_PWM;
+ ctrl->pulse = 0;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+ } else if ((ctrl->mode == MODE_AUTO) && (ctrl->type == CTRL_PWM)) {
+ ctrl->pulse = 0;
+ ctrl->enable = 1;
+ fan->cfg.rpm_min = 0;
+ fan->cfg.rpm_max = 0;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, pwm);
+ imanager_hwmon_write_fan_alert(&imgr->ec, nr, fan);
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_min);
+}
+
+static ssize_t
+show_pwm_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.pwm_max);
+}
+
+static ssize_t
+show_pwm_enable(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ uint mode = ctrl->mode - 1;
+
+ if (ctrl->mode == MODE_OFF)
+ mode = 0;
+
+ return sprintf(buf, "%u\n", mode);
+}
+
+enum pwm_enable { OFF, MANUAL, THERMAL_CRUISE };
+
+static ssize_t
+store_pwm_enable(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ int did = imgr->ec.hwmon.fan.attr[nr]->did;
+ unsigned long mode = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &mode);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&imgr->lock);
+
+ switch (mode) {
+ case OFF:
+ ctrl->mode = MODE_FULL;
+ ctrl->type = CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 100);
+ break;
+ case MANUAL:
+ ctrl->mode = MODE_MANUAL;
+ ctrl->type = CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+ break;
+ case THERMAL_CRUISE:
+ ctrl->mode = MODE_AUTO;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, did, 0);
+ break;
+ }
+
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ uint mode = (ctrl->type == CTRL_PWM) ? 1 : 0;
+
+ if (ctrl->mode == MODE_OFF)
+ mode = 0;
+
+ return sprintf(buf, "%u\n", mode);
+}
+
+static ssize_t
+store_pwm_mode(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ unsigned long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ ctrl->type = val ? CTRL_RPM : CTRL_PWM;
+ ctrl->enable = 1;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_temp_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%d\n", data->fan[nr].cfg.temp_min * 1000);
+}
+
+static ssize_t
+show_temp_max(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%u\n", data->fan[nr].cfg.temp_max * 1000);
+}
+
+static inline bool is_outside(int min, int max, int value)
+{
+ return (value < min) || (value > max);
+}
+
+static ssize_t
+show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct fan_dev_config *cfg = &data->fan[nr].cfg;
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&cfg->control;
+ bool is_alarm = (ctrl->mode == MODE_AUTO) ? 0 :
+ is_outside(cfg->temp_min, cfg->temp_max, cfg->temp);
+
+ return sprintf(buf, "%u\n", is_alarm);
+}
+
+static ssize_t store_temp_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ /* temperature in 1/10 degC */
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ val = val > 100 ? 100 : val;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ /*
+ * The iManager provides three different temperature limit values
+ * (stop, min, and max) where stop indicates a minimum temp value
+ * (threshold) from which the FAN will turn off. We are setting
+ * temp_stop to the same value as temp_min since it cannot be mapped
+ * to anything else.
+ */
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.temp_stop = val;
+ fan->cfg.temp_min = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_temp_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ struct fan_ctrl *ctrl = (struct fan_ctrl *)&fan->cfg.control;
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ val = val > 100 ? 100 : val;
+
+ /* do not apply value if not in cruise mode */
+ if (ctrl->mode != MODE_AUTO)
+ return count;
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.temp_max = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_pwm_min(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.pwm_min = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+store_pwm_max(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_device_data *imgr = data->imgr;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+ struct imanager_hwmon_smartfan *fan = &data->fan[nr];
+ long val = 0;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err < 0)
+ return err;
+
+ val = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ mutex_lock(&imgr->lock);
+ fan->cfg.pwm_max = val;
+ imanager_hwmon_write_fan_config(&imgr->ec, nr, fan);
+ mutex_unlock(&imgr->lock);
+
+ return count;
+}
+
+static ssize_t
+show_in_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", hwmon->adc.label[nr]);
+}
+
+static ssize_t
+show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%s\n", hwmon->fan.temp_label[nr]);
+}
+
+static ssize_t
+show_fan_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct imanager_hwmon_data *data = imanager_hwmon_update_device(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+ int nr = to_sensor_dev_attr(attr)->index - 1;
+
+ return sprintf(buf, "%s\n", hwmon->fan.label[nr]);
+}
+
+/*
+ * Sysfs callback functions
+ */
+
+static SENSOR_DEVICE_ATTR(in0_label, 0444, show_in_label, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_input, 0444, show_in, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_min, 0644, show_in_min, store_in_min, 0);
+static SENSOR_DEVICE_ATTR(in0_max, 0644, show_in_max, store_in_max, 0);
+static SENSOR_DEVICE_ATTR(in0_alarm, 0444, show_in_alarm, NULL, 0);
+
+static SENSOR_DEVICE_ATTR(in1_label, 0444, show_in_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_input, 0444, show_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(in1_min, 0644, show_in_min, store_in_min, 1);
+static SENSOR_DEVICE_ATTR(in1_max, 0644, show_in_max, store_in_max, 1);
+static SENSOR_DEVICE_ATTR(in1_alarm, 0444, show_in_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(in2_label, 0444, show_in_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_input, 0444, show_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(in2_min, 0644, show_in_min, store_in_min, 2);
+static SENSOR_DEVICE_ATTR(in2_max, 0644, show_in_max, store_in_max, 2);
+static SENSOR_DEVICE_ATTR(in2_alarm, 0444, show_in_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp1_label, 0444, show_temp_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_input, 0444, show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_min, 0644, show_temp_min, store_temp_min, 1);
+static SENSOR_DEVICE_ATTR(temp1_max, 0644, show_temp_max, store_temp_max, 1);
+static SENSOR_DEVICE_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(temp2_label, 0444, show_temp_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_input, 0444, show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_min, 0644, show_temp_min, store_temp_min, 2);
+static SENSOR_DEVICE_ATTR(temp2_max, 0644, show_temp_max, store_temp_max, 2);
+static SENSOR_DEVICE_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp3_label, 0444, show_temp_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_input, 0444, show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp3_min, 0644, show_temp_min, store_temp_min, 3);
+static SENSOR_DEVICE_ATTR(temp3_max, 0644, show_temp_max, store_temp_max, 3);
+static SENSOR_DEVICE_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(fan1_label, 0444, show_fan_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_input, 0444, show_fan_in, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_min, 0644, show_fan_min, store_fan_min, 1);
+static SENSOR_DEVICE_ATTR(fan1_max, 0644, show_fan_max, store_fan_max, 1);
+static SENSOR_DEVICE_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 1);
+
+static SENSOR_DEVICE_ATTR(fan2_label, 0444, show_fan_label, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_input, 0444, show_fan_in, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan2_min, 0644, show_fan_min, store_fan_min, 2);
+static SENSOR_DEVICE_ATTR(fan2_max, 0644, show_fan_max, store_fan_max, 2);
+static SENSOR_DEVICE_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 2);
+
+static SENSOR_DEVICE_ATTR(fan3_label, 0444, show_fan_label, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_input, 0444, show_fan_in, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan3_min, 0644, show_fan_min, store_fan_min, 3);
+static SENSOR_DEVICE_ATTR(fan3_max, 0644, show_fan_max, store_fan_max, 3);
+static SENSOR_DEVICE_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1, 0644, show_pwm, store_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm1_min, 0644, show_pwm_min, store_pwm_min, 1);
+static SENSOR_DEVICE_ATTR(pwm1_max, 0644, show_pwm_max, store_pwm_max, 1);
+static SENSOR_DEVICE_ATTR(pwm1_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 1);
+static SENSOR_DEVICE_ATTR(pwm1_mode, 0644, show_pwm_mode, store_pwm_mode, 1);
+
+static SENSOR_DEVICE_ATTR(pwm2, 0644, show_pwm, store_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm2_min, 0644, show_pwm_min, store_pwm_min, 2);
+static SENSOR_DEVICE_ATTR(pwm2_max, 0644, show_pwm_max, store_pwm_max, 2);
+static SENSOR_DEVICE_ATTR(pwm2_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 2);
+static SENSOR_DEVICE_ATTR(pwm2_mode, 0644, show_pwm_mode, store_pwm_mode, 2);
+
+static SENSOR_DEVICE_ATTR(pwm3, 0644, show_pwm, store_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm3_min, 0644, show_pwm_min, store_pwm_min, 3);
+static SENSOR_DEVICE_ATTR(pwm3_max, 0644, show_pwm_max, store_pwm_max, 3);
+static SENSOR_DEVICE_ATTR(pwm3_enable, 0644, show_pwm_enable,
+ store_pwm_enable, 3);
+static SENSOR_DEVICE_ATTR(pwm3_mode, 0644, show_pwm_mode, store_pwm_mode, 3);
+
+static SENSOR_DEVICE_ATTR(curr1_input, 0444, show_in, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_min, 0644, show_in_min, store_in_min, 4);
+static SENSOR_DEVICE_ATTR(curr1_max, 0644, show_in_max, store_in_max, 4);
+static SENSOR_DEVICE_ATTR(curr1_alarm, 0444, show_in_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_average, 0444, show_in_average, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_lowest, 0444, show_in_lowest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_highest, 0444, show_in_highest, NULL, 4);
+static SENSOR_DEVICE_ATTR(curr1_reset_history, 0200, NULL,
+ store_in_reset_history, 4);
+
+static SENSOR_DEVICE_ATTR(cpu0_vid, 0444, show_in, NULL, 3);
+
+static struct attribute *imanager_in_attributes[] = {
+ &sensor_dev_attr_in0_label.dev_attr.attr,
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ &sensor_dev_attr_in0_min.dev_attr.attr,
+ &sensor_dev_attr_in0_max.dev_attr.attr,
+ &sensor_dev_attr_in0_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_in1_label.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in1_min.dev_attr.attr,
+ &sensor_dev_attr_in1_max.dev_attr.attr,
+ &sensor_dev_attr_in1_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_in2_label.dev_attr.attr,
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in2_min.dev_attr.attr,
+ &sensor_dev_attr_in2_max.dev_attr.attr,
+ &sensor_dev_attr_in2_alarm.dev_attr.attr,
+
+ NULL
+};
+
+#define to_dev(obj) container_of(obj, struct device, kobj)
+
+static umode_t
+imanager_in_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+ if (hwmon->adc.num <= EC_MAX_ADC_NUM)
+ return attr->mode;
+
+ return 0;
+}
+
+static const struct attribute_group imanager_group_in = {
+ .attrs = imanager_in_attributes,
+ .is_visible = imanager_in_is_visible,
+};
+
+static struct attribute *imanager_other_attributes[] = {
+ &sensor_dev_attr_curr1_input.dev_attr.attr,
+ &sensor_dev_attr_curr1_min.dev_attr.attr,
+ &sensor_dev_attr_curr1_max.dev_attr.attr,
+ &sensor_dev_attr_curr1_alarm.dev_attr.attr,
+ &sensor_dev_attr_curr1_average.dev_attr.attr,
+ &sensor_dev_attr_curr1_lowest.dev_attr.attr,
+ &sensor_dev_attr_curr1_highest.dev_attr.attr,
+ &sensor_dev_attr_curr1_reset_history.dev_attr.attr,
+
+ &sensor_dev_attr_cpu0_vid.dev_attr.attr,
+
+ NULL
+};
+
+static umode_t imanager_other_is_visible(struct kobject *kobj,
+ struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_hwmon_device *hwmon = &data->imgr->ec.hwmon;
+
+ /*
+ * There are either 3 or 5 VINs available
+ * vin3 is current monitoring
+ * vin4 is CPU VID
+ */
+ if (hwmon->adc.num == EC_MAX_ADC_NUM)
+ return attr->mode;
+
+ return 0;
+}
+
+static const struct attribute_group imanager_group_other = {
+ .attrs = imanager_other_attributes,
+ .is_visible = imanager_other_is_visible,
+};
+
+static struct attribute *imanager_fan_attributes[] = {
+ &sensor_dev_attr_fan1_label.dev_attr.attr,
+ &sensor_dev_attr_fan1_input.dev_attr.attr,
+ &sensor_dev_attr_fan1_min.dev_attr.attr,
+ &sensor_dev_attr_fan1_max.dev_attr.attr,
+ &sensor_dev_attr_fan1_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_fan2_label.dev_attr.attr,
+ &sensor_dev_attr_fan2_input.dev_attr.attr,
+ &sensor_dev_attr_fan2_min.dev_attr.attr,
+ &sensor_dev_attr_fan2_max.dev_attr.attr,
+ &sensor_dev_attr_fan2_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_fan3_label.dev_attr.attr,
+ &sensor_dev_attr_fan3_input.dev_attr.attr,
+ &sensor_dev_attr_fan3_min.dev_attr.attr,
+ &sensor_dev_attr_fan3_max.dev_attr.attr,
+ &sensor_dev_attr_fan3_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_temp1_label.dev_attr.attr,
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_min.dev_attr.attr,
+ &sensor_dev_attr_temp1_max.dev_attr.attr,
+ &sensor_dev_attr_temp1_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_temp2_label.dev_attr.attr,
+ &sensor_dev_attr_temp2_input.dev_attr.attr,
+ &sensor_dev_attr_temp2_min.dev_attr.attr,
+ &sensor_dev_attr_temp2_max.dev_attr.attr,
+ &sensor_dev_attr_temp2_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_temp3_label.dev_attr.attr,
+ &sensor_dev_attr_temp3_input.dev_attr.attr,
+ &sensor_dev_attr_temp3_min.dev_attr.attr,
+ &sensor_dev_attr_temp3_max.dev_attr.attr,
+ &sensor_dev_attr_temp3_alarm.dev_attr.attr,
+
+ &sensor_dev_attr_pwm1.dev_attr.attr,
+ &sensor_dev_attr_pwm1_min.dev_attr.attr,
+ &sensor_dev_attr_pwm1_max.dev_attr.attr,
+ &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm1_mode.dev_attr.attr,
+
+ &sensor_dev_attr_pwm2.dev_attr.attr,
+ &sensor_dev_attr_pwm2_min.dev_attr.attr,
+ &sensor_dev_attr_pwm2_max.dev_attr.attr,
+ &sensor_dev_attr_pwm2_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm2_mode.dev_attr.attr,
+
+ &sensor_dev_attr_pwm3.dev_attr.attr,
+ &sensor_dev_attr_pwm3_min.dev_attr.attr,
+ &sensor_dev_attr_pwm3_max.dev_attr.attr,
+ &sensor_dev_attr_pwm3_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm3_mode.dev_attr.attr,
+
+ NULL
+};
+
+static umode_t
+imanager_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+ struct device *dev = to_dev(kobj);
+ struct imanager_hwmon_data *data = dev_get_drvdata(dev);
+ struct imanager_fan_device *fan = &data->imgr->ec.hwmon.fan;
+
+ if ((index >= 0) && (index <= 14)) { /* fan */
+ if (!fan->attr[index / 5])
+ return 0;
+ } else if ((index >= 15) && (index <= 29)) { /* temp */
+ if (!fan->attr[(index - 15) / 5])
+ return 0;
+ } else if ((index >= 30) && (index <= 34)) { /* pwm */
+ if (!fan->attr[(index - 30) / 5])
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static const struct attribute_group imanager_group_fan = {
+ .attrs = imanager_fan_attributes,
+ .is_visible = imanager_fan_is_visible,
+};
+
+static int imanager_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct imanager_hwmon_device *hwmon = &ec->hwmon;
+ struct imanager_hwmon_data *data;
+ struct device *hwmon_dev;
+ int i, num_attr_groups = 0;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->imgr = imgr;
+ platform_set_drvdata(pdev, data);
+
+ /* read fan control settings */
+ for (i = 0; i < hwmon->fan.num; i++)
+ imanager_hwmon_read_fan_ctrl(ec, i, &data->fan[i]);
+
+ data->groups[num_attr_groups++] = &imanager_group_in;
+
+ if (hwmon->adc.num > 3)
+ data->groups[num_attr_groups++] = &imanager_group_other;
+
+ if (hwmon->fan.num)
+ data->groups[num_attr_groups++] = &imanager_group_fan;
+
+ hwmon_dev = devm_hwmon_device_register_with_groups(dev,
+ "imanager_hwmon",
+ data, data->groups);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver imanager_hwmon_driver = {
+ .driver = {
+ .name = "imanager-hwmon",
+ },
+ .probe = imanager_hwmon_probe,
+};
+
+module_platform_driver(imanager_hwmon_driver);
+
+MODULE_DESCRIPTION("Advantech iManager HWmon Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-hwmon");
--
2.10.1
--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" 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
* [PATCH v4 2/6] Add Advantech iManager GPIO driver
From: Richard Vidal-Dorsch @ 2016-11-02 8:37 UTC (permalink / raw)
To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou
In-Reply-To: <20161102083751.6335-1-richard.dorsch@gmail.com>
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/gpio/Kconfig | 10 +++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-imanager.c | 155 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 166 insertions(+)
create mode 100644 drivers/gpio/gpio-imanager.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index d011cb8..52e371f 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -884,6 +884,16 @@ config HTC_EGPIO
several HTC phones. It provides basic support for input
pins, output pins, and irqs.
+config GPIO_IMANAGER
+ tristate "Advantech iManager GPIO"
+ depends on MFD_IMANAGER
+ help
+ This enables support for the iManager GPIO interface on some
+ Advantech SOM, MIO, AIMB, and PCM modules/boards.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-imanager.
+
config GPIO_JANZ_TTL
tristate "Janz VMOD-TTL Digital IO Module"
depends on MFD_JANZ_CMODIO
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index ab28a2d..e1250eb 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o
obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o
obj-$(CONFIG_HTC_EGPIO) += gpio-htc-egpio.o
obj-$(CONFIG_GPIO_ICH) += gpio-ich.o
+obj-$(CONFIG_GPIO_IMANAGER) += gpio-imanager.o
obj-$(CONFIG_GPIO_IOP) += gpio-iop.o
obj-$(CONFIG_GPIO_IT87) += gpio-it87.o
obj-$(CONFIG_GPIO_JANZ_TTL) += gpio-janz-ttl.o
diff --git a/drivers/gpio/gpio-imanager.c b/drivers/gpio/gpio-imanager.c
new file mode 100644
index 0000000..f60592b
--- /dev/null
+++ b/drivers/gpio/gpio-imanager.c
@@ -0,0 +1,155 @@
+/*
+ * Advantech iManager GPIO driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#define EC_GPIOF_DIR_OUT BIT(6)
+#define EC_GPIOF_DIR_IN BIT(7)
+
+struct imanager_gpio_data {
+ struct imanager_device_data *imgr;
+ struct gpio_chip chip;
+};
+
+static int imanager_gpio_direction_in(struct gpio_chip *chip, uint offset)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+
+ mutex_lock(&imgr->lock);
+ imanager_write8(&imgr->ec, EC_CMD_GPIO_DIR_WR, attr->did,
+ EC_GPIOF_DIR_IN);
+ mutex_unlock(&imgr->lock);
+
+ return 0;
+}
+
+static int
+imanager_gpio_direction_out(struct gpio_chip *chip, uint offset, int val)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+
+ mutex_lock(&imgr->lock);
+ imanager_write8(&imgr->ec, EC_CMD_GPIO_DIR_WR, attr->did,
+ EC_GPIOF_DIR_OUT);
+ mutex_unlock(&imgr->lock);
+
+ return 0;
+}
+
+static int imanager_gpio_get_direction(struct gpio_chip *chip, uint offset)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_read8(&imgr->ec, EC_CMD_GPIO_DIR_RD, attr->did);
+ mutex_unlock(&imgr->lock);
+
+ return ret & EC_GPIOF_DIR_IN ? GPIOF_DIR_IN : GPIOF_DIR_OUT;
+}
+
+static int imanager_gpio_get(struct gpio_chip *chip, uint offset)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+ int ret;
+
+ mutex_lock(&imgr->lock);
+ ret = imanager_read8(&imgr->ec, EC_CMD_HWP_RD, attr->did);
+ mutex_unlock(&imgr->lock);
+
+ return ret;
+}
+
+static void imanager_gpio_set(struct gpio_chip *chip, uint offset, int val)
+{
+ struct imanager_gpio_data *data = gpiochip_get_data(chip);
+ struct imanager_device_data *imgr = data->imgr;
+ struct imanager_device_attribute *attr = imgr->ec.gpio.attr[offset];
+
+ mutex_lock(&imgr->lock);
+ imanager_write8(&imgr->ec, EC_CMD_HWP_WR, attr->did, val);
+ mutex_unlock(&imgr->lock);
+}
+
+static int imanager_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr = dev_get_drvdata(dev->parent);
+ struct imanager_gpio_data *gpio;
+ struct gpio_chip *chip;
+ int ret;
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->imgr = imgr;
+
+ platform_set_drvdata(pdev, gpio);
+
+ chip = &gpio->chip;
+
+ chip->owner = THIS_MODULE;
+ chip->parent = dev;
+ chip->label = "gpio-imanager";
+ chip->base = -1;
+ chip->ngpio = imgr->ec.gpio.num;
+ chip->get = imanager_gpio_get;
+ chip->set = imanager_gpio_set;
+ chip->direction_input = imanager_gpio_direction_in;
+ chip->direction_output = imanager_gpio_direction_out;
+ chip->get_direction = imanager_gpio_get_direction;
+ if (!chip->ngpio) {
+ dev_err(dev, "No GPIO pins detected\n");
+ return -ENODEV;
+ }
+
+ ret = devm_gpiochip_add_data(dev, chip, gpio);
+ if (ret < 0) {
+ dev_err(dev, "Could not register GPIO chip\n");
+ return ret;
+ }
+
+ dev_info(dev, "GPIO initialized with %d pins\n", chip->ngpio);
+
+ return 0;
+}
+
+static struct platform_driver imanager_gpio_driver = {
+ .driver = {
+ .name = "imanager-gpio",
+ },
+ .probe = imanager_gpio_probe,
+};
+
+module_platform_driver(imanager_gpio_driver);
+
+MODULE_DESCRIPTION("Advantech iManager GPIO Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-gpio");
--
2.10.1
^ permalink raw reply related
* [PATCH v4 1/6] Add Advantech iManager MFD core driver
From: Richard Vidal-Dorsch @ 2016-11-02 8:37 UTC (permalink / raw)
To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang, andrew.chou
In-Reply-To: <20161102083751.6335-1-richard.dorsch@gmail.com>
This patch adds Advantech iManager Embedded Controller MFD core driver.
This mfd core dirver provides an interface for GPIO, I2C, HWmon,
Watchdog, and Backlight/Brightness control.
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com>
---
drivers/mfd/Kconfig | 18 +
drivers/mfd/Makefile | 1 +
drivers/mfd/imanager-core.c | 941 ++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/imanager-ec.h | 228 ++++++++++
include/linux/mfd/imanager.h | 221 ++++++++++
5 files changed, 1409 insertions(+)
create mode 100644 drivers/mfd/imanager-core.c
create mode 100644 include/linux/mfd/imanager-ec.h
create mode 100644 include/linux/mfd/imanager.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index c6df644..294c19d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -388,6 +388,24 @@ config MFD_INTEL_QUARK_I2C_GPIO
their respective IO driver.
The GPIO exports a total amount of 8 interrupt-capable GPIOs.
+config MFD_IMANAGER
+ tristate "Advantech iManager EC device"
+ select MFD_CORE
+ help
+ This is the core driver for Advantech iManager Embedded Controller
+ found on some Advantech SOM, MIO, AIMB, and PCM modules/boards. The
+ EC may provide functions like GPIO, I2C bus, HW monitoring, Watchdog,
+ and backlight/brightness control.
+
+ The following Advantech boards are supported:
+ * All SOM modules newer than SOM-5788
+ * MIO-5250/5251/5270/5271/5272/5290 and newer
+ * PCM-9389/9365/9376 and newer
+ * AIMB-273/274/230/231 and newer
+
+ This driver can also be built as a module. If so, the module
+ will be called imanager-core.
+
config LPC_ICH
tristate "Intel ICH LPC"
depends on PCI
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 9834e66..e4b0a4d 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -156,6 +156,7 @@ obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o
obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o
obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
+obj-$(CONFIG_MFD_IMANAGER) += imanager-core.o
obj-$(CONFIG_MFD_KEMPLD) += kempld-core.o
obj-$(CONFIG_MFD_INTEL_QUARK_I2C_GPIO) += intel_quark_i2c_gpio.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
diff --git a/drivers/mfd/imanager-core.c b/drivers/mfd/imanager-core.c
new file mode 100644
index 0000000..2ec1b79
--- /dev/null
+++ b/drivers/mfd/imanager-core.c
@@ -0,0 +1,941 @@
+/*
+ * Advantech iManager MFD driver
+ * Partially derived from kempld-core
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/byteorder/generic.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/imanager.h>
+#include <linux/mfd/imanager-ec.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+
+enum kinds { IT8518, IT8528 };
+
+static struct platform_device *imanager_pdev;
+
+static const char * const chip_names[] = {
+ "it8518",
+ "it8528",
+ NULL
+};
+
+static const struct imanager_ec_device ecdev_table[] = {
+ /* GPIO */
+ { IMANAGER_EC_DEVICE(GPIO0, GPIO, -1) },
+ { IMANAGER_EC_DEVICE(GPIO1, GPIO, -1) },
+ { IMANAGER_EC_DEVICE(GPIO2, GPIO, -1) },
+ { IMANAGER_EC_DEVICE(GPIO3, GPIO, -1) },
+ { IMANAGER_EC_DEVICE(GPIO4, GPIO, -1) },
+ { IMANAGER_EC_DEVICE(GPIO5, GPIO, -1) },
+ { IMANAGER_EC_DEVICE(GPIO6, GPIO, -1) },
+ { IMANAGER_EC_DEVICE(GPIO7, GPIO, -1) },
+ /* FAN */
+ { IMANAGER_EC_DEVICE(CPUFAN_2P, PWM, 2) },
+ { IMANAGER_EC_DEVICE(CPUFAN_4P, PWM, 4) },
+ { IMANAGER_EC_DEVICE(SYSFAN1_2P, PWM, 2) },
+ { IMANAGER_EC_DEVICE(SYSFAN1_4P, PWM, 4) },
+ { IMANAGER_EC_DEVICE(SYSFAN2_2P, PWM, 2) },
+ { IMANAGER_EC_DEVICE(SYSFAN2_4P, PWM, 4) },
+ /* ADC */
+ { IMANAGER_EC_DEVICE(ADC12VS0, ADC, 1) },
+ { IMANAGER_EC_DEVICE(ADC12VS0_2, ADC, 2) },
+ { IMANAGER_EC_DEVICE(ADC12VS0_10, ADC, 10) },
+ { IMANAGER_EC_DEVICE(ADC5VS0, ADC, 1) },
+ { IMANAGER_EC_DEVICE(ADC5VS0_2, ADC, 2) },
+ { IMANAGER_EC_DEVICE(ADC5VS0_10, ADC, 10) },
+ { IMANAGER_EC_DEVICE(ADC5VS5, ADC, 1) },
+ { IMANAGER_EC_DEVICE(ADC5VS5_2, ADC, 2) },
+ { IMANAGER_EC_DEVICE(ADC5VS5_10, ADC, 10) },
+ { IMANAGER_EC_DEVICE(ADC33VS0, ADC, 1) },
+ { IMANAGER_EC_DEVICE(ADC33VS0_2, ADC, 2) },
+ { IMANAGER_EC_DEVICE(ADC33VS0_10, ADC, 10) },
+ { IMANAGER_EC_DEVICE(CMOSBAT, ADC, 1) },
+ { IMANAGER_EC_DEVICE(CMOSBAT_2, ADC, 2) },
+ { IMANAGER_EC_DEVICE(CMOSBAT_10, ADC, 10) },
+ { IMANAGER_EC_DEVICE(VCOREA, ADC, 1) },
+ { IMANAGER_EC_DEVICE(CURRENT, ADC, 1) },
+ /* I2C/SMBus */
+ { IMANAGER_EC_DEVICE(SMBEEPROM, SMB, -1) },
+ { IMANAGER_EC_DEVICE(I2COEM, SMB, -1) },
+ { IMANAGER_EC_DEVICE(SMBOEM0, SMB, -1) },
+ { IMANAGER_EC_DEVICE(SMBPECI, SMB, -1) },
+ /* Backlight/Brightness */
+ { IMANAGER_EC_DEVICE(BRIGHTNESS, PWM, -1) },
+ { IMANAGER_EC_DEVICE(BRIGHTNESS2, PWM, -1) },
+ /* Watchdog */
+ { IMANAGER_EC_DEVICE(WDIRQ, IRQ, -1) },
+ { IMANAGER_EC_DEVICE(WDNMI, GPIO, -1) },
+ { 0 }
+};
+
+/**
+ * iManager I/O
+ */
+
+enum imanager_io_buffer_status { IS_CLEARED = 0, IS_SET };
+
+#define CHECK_BIT(reg, bit) ((reg) & (bit))
+
+static inline int check_io28_ready(uint bit, uint state)
+{
+ int ret, i = 0;
+
+ do {
+ ret = inb(IT8528_CMD_PORT);
+ if (CHECK_BIT(ret, bit) == state)
+ return 0;
+ usleep_range(EC_DELAY_MIN, EC_DELAY_MAX);
+ } while (i++ < EC_MAX_RETRY);
+
+ return -ETIME;
+}
+
+static inline int ec_inb(int addr, int reg)
+{
+ outb(reg, addr);
+ return inb(addr + 1);
+}
+
+static inline void ec_outb(int addr, int reg, int val)
+{
+ outb(reg, addr);
+ outb(val, addr + 1);
+}
+
+static inline int ec_io28_inb(int addr, int reg)
+{
+ int ret;
+
+ ret = check_io28_ready(EC_IO28_INBUF, IS_CLEARED);
+ if (ret)
+ return ret;
+
+ /* prevent firmware lock */
+ inb(addr - 1);
+
+ outb(reg, addr);
+
+ ret = check_io28_ready(EC_IO28_OUTBUF, IS_SET);
+ if (ret)
+ return ret;
+
+ return inb(addr - 1);
+}
+
+static inline int ec_io28_outb(int addr, int reg, int val)
+{
+ int ret;
+
+ ret = check_io28_ready(EC_IO28_INBUF, IS_CLEARED);
+ if (ret)
+ return ret;
+
+ outb(reg, addr);
+
+ ret = check_io28_ready(EC_IO28_INBUF, IS_CLEARED);
+ if (ret)
+ return ret;
+
+ outb(val, addr - 1);
+
+ return 0;
+}
+
+static inline int ec_io18_read(int cmd)
+{
+ return ec_inb(IT8518_CMD_PORT, cmd);
+}
+
+static inline int ec_io18_write(int cmd, int value)
+{
+ ec_outb(IT8518_CMD_PORT, cmd, value);
+
+ return 0;
+}
+
+static inline int ec_io28_read(int cmd)
+{
+ return ec_io28_inb(IT8528_CMD_PORT, cmd + EC_CMD_OFFSET_READ);
+}
+
+static inline int ec_io28_write(int cmd, int value)
+{
+ return ec_io28_outb(IT8528_CMD_PORT, cmd + EC_CMD_OFFSET_WRITE, value);
+}
+
+static int imanager_check_ec_ready(struct imanager_io_ops *io)
+{
+ int i = 0;
+
+ do {
+ if (!io->read(EC_CMD_CHK_RDY))
+ return 0;
+ usleep_range(EC_DELAY_MIN, EC_DELAY_MAX);
+ } while (i++ < EC_MAX_RETRY);
+
+ return -ETIME;
+}
+
+/**
+ * iManager Device Configuration
+ */
+
+static void imanager_add_attribute(struct imanager_ec_data *ec,
+ struct imanager_device_attribute *attr)
+{
+ struct imanager_gpio_device *gpio = &ec->gpio;
+ struct imanager_adc_device *adc = &ec->hwmon.adc;
+ struct imanager_fan_device *fan = &ec->hwmon.fan;
+ struct imanager_i2c_device *i2c = &ec->i2c;
+ struct imanager_backlight_device *bl = &ec->bl;
+ struct imanager_watchdog_device *wdt = &ec->wdt;
+
+ switch (attr->ecdev->type) {
+ case GPIO:
+ switch (attr->did) {
+ case GPIO0:
+ case GPIO1:
+ case GPIO2:
+ case GPIO3:
+ case GPIO4:
+ case GPIO5:
+ case GPIO6:
+ case GPIO7:
+ gpio->attr[gpio->num++] = attr;
+ break;
+ case WDNMI:
+ wdt->attr[1] = attr;
+ wdt->num++;
+ break;
+ }
+ case ADC:
+ switch (attr->did) {
+ case ADC12VS0:
+ case ADC12VS0_2:
+ case ADC12VS0_10:
+ adc->attr[0] = attr;
+ adc->label[0] = "+12VS0";
+ adc->num++;
+ break;
+ case ADC5VS5:
+ case ADC5VS5_2:
+ case ADC5VS5_10:
+ adc->attr[1] = attr;
+ adc->label[1] = "+5VS0";
+ adc->num++;
+ break;
+ case CMOSBAT:
+ case CMOSBAT_2:
+ case CMOSBAT_10:
+ adc->attr[2] = attr;
+ adc->label[2] = "+3.3VS0";
+ adc->num++;
+ break;
+ case VCOREA:
+ case ADC5VS0:
+ case ADC5VS0_2:
+ case ADC5VS0_10:
+ adc->attr[3] = attr;
+ adc->num++;
+ break;
+ case CURRENT:
+ case ADC33VS0:
+ case ADC33VS0_2:
+ case ADC33VS0_10:
+ adc->attr[4] = attr;
+ adc->num++;
+ break;
+ }
+ case PWM:
+ switch (attr->did) {
+ case CPUFAN_2P:
+ case CPUFAN_4P:
+ fan->attr[0] = attr;
+ fan->label[0] = "FAN CPU";
+ fan->temp_label[0] = "Temp CPU";
+ fan->num++;
+ break;
+ case SYSFAN1_2P:
+ case SYSFAN1_4P:
+ fan->attr[1] = attr;
+ fan->label[1] = "FAN SYS1";
+ fan->temp_label[1] = "Temp SYS1";
+ fan->num++;
+ break;
+ case SYSFAN2_2P:
+ case SYSFAN2_4P:
+ fan->attr[2] = attr;
+ fan->label[2] = "FAN SYS2";
+ fan->temp_label[2] = "Temp SYS2";
+ fan->num++;
+ break;
+ case BRIGHTNESS:
+ bl->attr[0] = attr;
+ bl->brightness[0] = EC_OFFSET_BRIGHTNESS1;
+ bl->num++;
+ break;
+ case BRIGHTNESS2:
+ bl->attr[1] = attr;
+ bl->brightness[1] = EC_OFFSET_BRIGHTNESS2;
+ bl->num++;
+ break;
+ }
+ case SMB:
+ switch (attr->did) {
+ case SMBEEPROM:
+ i2c->attr[SMB_EEP] = attr;
+ i2c->num++;
+ break;
+ case I2COEM:
+ i2c->attr[I2C_OEM] = attr;
+ i2c->num++;
+ break;
+ case SMBOEM0:
+ i2c->attr[SMB_1] = attr;
+ i2c->num++;
+ break;
+ case SMBPECI:
+ i2c->attr[SMB_PECI] = attr;
+ i2c->num++;
+ break;
+ }
+ case IRQ:
+ if (attr->did == WDIRQ) {
+ wdt->attr[0] = attr;
+ wdt->num++;
+ break;
+ }
+ }
+}
+
+enum imanager_device_table_type { DEVID = 0, HWPIN, POLARITY };
+
+static int imanager_read_device_config(struct imanager_ec_data *ec)
+{
+ struct imanager_ec_message msgs[] = {
+ { IMANAGER_MSG_SIMPLE(EC_MAX_DID, 0, DEVID, NULL) },
+ { IMANAGER_MSG_SIMPLE(EC_MAX_DID, 0, HWPIN, NULL) },
+ { IMANAGER_MSG_SIMPLE(EC_MAX_DID, 0, POLARITY, NULL) },
+ };
+ struct imanager_device_attribute *attr;
+ int i, j, ret;
+
+ /* Read iManager device configurations */
+ for (i = 0; i < ARRAY_SIZE(msgs); i++) {
+ ret = imanager_read(ec, EC_CMD_DEV_TBL_RD, &msgs[i]);
+ if (ret)
+ return ret;
+ }
+
+ /* Generate iManager device atributes */
+ for (i = 0; i < EC_MAX_DID && msgs[DEVID].u.data[i]; i++) {
+ attr = &ec->attr[i];
+ for (j = 0; j < ARRAY_SIZE(ecdev_table); j++) {
+ if (ecdev_table[j].did == msgs[DEVID].u.data[i]) {
+ attr->did = msgs[DEVID].u.data[i];
+ attr->hwp = msgs[HWPIN].u.data[i];
+ attr->pol = msgs[POLARITY].u.data[i];
+ attr->ecdev = &ecdev_table[j];
+ imanager_add_attribute(ec, attr);
+ break;
+ }
+ }
+ }
+
+ if (ec->gpio.num)
+ ec->features |= IMANAGER_FEATURE_GPIO;
+ if (ec->hwmon.adc.num)
+ ec->features |= IMANAGER_FEATURE_HWMON_ADC;
+ if (ec->hwmon.fan.num)
+ ec->features |= IMANAGER_FEATURE_HWMON_FAN;
+ if (ec->i2c.num)
+ ec->features |= IMANAGER_FEATURE_SMBUS;
+ if (ec->bl.num)
+ ec->features |= IMANAGER_FEATURE_BACKLIGHT;
+ if (ec->wdt.num)
+ ec->features |= IMANAGER_FEATURE_WDT;
+
+ return 0;
+}
+
+static const char *project_code_to_str(unsigned int code)
+{
+ switch ((char)code) {
+ case 'V':
+ return "release";
+ case 'X':
+ return "debug";
+ case 'A' ... 'U':
+ case 'Y':
+ case 'Z':
+ return "custom";
+ }
+
+ return "unspecified";
+}
+
+static int imanager_read_firmware_version(struct imanager_ec_data *ec)
+{
+ char pcb_name[IMANAGER_PCB_NAME_LEN] = { 0 };
+ struct imanager_info *info = &ec->info;
+ struct imanager_ec_message msg = {
+ .rlen = ARRAY_SIZE(pcb_name) - 1,
+ .wlen = 0,
+ .param = 0,
+ .data = pcb_name,
+ };
+ struct imanager_ec_version ver;
+ unsigned int val;
+ int ret;
+
+ ret = imanager_read_ram(ec, EC_RAM_ACPI, EC_OFFSET_FW_RELEASE,
+ (u8 *)&ver, sizeof(ver));
+ if (ret < 0)
+ return ret;
+
+ val = cpu_to_be16(ver.kernel);
+ info->kernel_major = EC_KERNEL_MAJOR(val);
+ info->kernel_minor = EC_KERNEL_MINOR(val);
+
+ val = cpu_to_be16(ver.firmware);
+ info->firmware_major = EC_FIRMWARE_MAJOR(val);
+ info->firmware_minor = EC_FIRMWARE_MINOR(val);
+
+ val = cpu_to_be16(ver.project_code);
+ info->type = project_code_to_str(EC_PROJECT_CODE(val));
+
+ /*
+ * The PCB name string, in some FW releases, is not Null-terminated,
+ * so we need to read a fixed amount of chars. Also, the name length
+ * may vary by one char (SOM6867 vs. SOM-6867).
+ */
+ ret = imanager_read(ec, EC_CMD_FW_INFO_RD, &msg);
+ if (ret)
+ return ret;
+
+ if (!strchr(pcb_name, '-'))
+ pcb_name[IMANAGER_PCB_NAME_LEN - 2] = '\0';
+
+ return scnprintf(info->version, sizeof(info->version),
+ "%s_k%d.%d_f%d.%d_%s", pcb_name, info->kernel_major,
+ info->kernel_minor, info->firmware_major,
+ info->firmware_minor, info->type);
+}
+
+static int imanager_ec_init(struct imanager_ec_data *ec)
+{
+ int ret;
+
+ /* Prevent firmware lock */
+ inb(IT8528_DAT_PORT);
+ inb(IT8518_DAT_PORT);
+
+ ret = imanager_read_firmware_version(ec);
+ if (ret < 0)
+ return ret;
+
+ return imanager_read_device_config(ec);
+}
+
+static inline void data_to_ec(struct imanager_io_ops *io, u8 *data, u8 len,
+ int offset)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ io->write(offset++, data[i]);
+}
+
+static inline void data_from_ec(struct imanager_io_ops *io, u8 *data, u8 len,
+ int offset)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ data[i] = io->read(offset++);
+}
+
+static int imanager_msg_xfer(struct imanager_ec_data *ec, u8 cmd,
+ struct imanager_ec_message *msg, bool payload)
+{
+ int ret;
+ int offset = EC_MSG_OFFSET_DATA;
+
+ ret = imanager_check_ec_ready(&ec->io);
+ if (ret)
+ return ret;
+
+ ec->io.write(EC_MSG_OFFSET_PARAM, msg->param);
+
+ if (msg->wlen) {
+ if (msg->data) {
+ data_to_ec(&ec->io, msg->data, msg->wlen, offset);
+ ec->io.write(EC_MSG_OFFSET_LEN, msg->wlen);
+ } else {
+ data_to_ec(&ec->io, msg->u.data, msg->wlen, offset);
+ }
+ }
+
+ /* Execute command */
+ ec->io.write(EC_MSG_OFFSET_CMD, cmd);
+ ret = imanager_check_ec_ready(&ec->io);
+ if (ret)
+ return ret;
+
+ /* GPIO and I2C have different success return values */
+ ret = ec->io.read(EC_MSG_OFFSET_STATUS);
+ if ((ret != EC_F_SUCCESS) && !(ret & EC_F_CMD_COMPLETE))
+ return -EFAULT;
+ /*
+ * EC I2C may return an error code which we need to handoff
+ * to the caller
+ */
+ else if (ret & 0x007e)
+ return ret;
+
+ if (msg->rlen) {
+ if (msg->rlen == EC_F_HWMON_MSG)
+ msg->rlen = ec->io.read(EC_MSG_OFFSET_LEN);
+ if (payload) /* i2c, hwmon, wdt */
+ offset = EC_MSG_OFFSET_PAYLOAD;
+ if (msg->data)
+ data_from_ec(&ec->io, msg->data, msg->rlen, offset);
+ else
+ data_from_ec(&ec->io, msg->u.data, msg->rlen, offset);
+ }
+
+ return 0;
+}
+
+/**
+ * imanager_read_ram - read 'size' amount of data @ 'offset' of 'ram_type'
+ * @ec: imanager_ec_data structure describing the EC
+ * @ram_type: RAM type such as ACPI, HW, or EXternal
+ * @offset: offset within the RAM segment
+ * @data: data pointer
+ * @len: data length
+ */
+int imanager_read_ram(struct imanager_ec_data *ec, int ram_type, u8 offset,
+ u8 *data, u8 len)
+{
+ int ret;
+
+ ret = imanager_check_ec_ready(&ec->io);
+ if (ret)
+ return ret;
+
+ ec->io.write(EC_MSG_OFFSET_PARAM, ram_type);
+ ec->io.write(EC_MSG_OFFSET_DATA, offset);
+ ec->io.write(EC_MSG_OFFSET_LEN, len);
+ ec->io.write(EC_MSG_OFFSET_CMD, EC_CMD_RAM_RD);
+
+ ret = imanager_check_ec_ready(&ec->io);
+ if (ret)
+ return ret;
+
+ ret = ec->io.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_F_SUCCESS)
+ return -EIO;
+
+ data_from_ec(&ec->io, data, len, EC_MSG_OFFSET_RAM_DATA);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_read_ram);
+
+/**
+ * imanager_write_ram - write 'len' amount of data @ 'offset' of 'ram_type'
+ * @ec: imanager_ec_data structure describing the EC
+ * @ram_type: RAM type such as ACPI, HW, or EXternal
+ * @offset: offset within the RAM segment
+ * @data: data pointer
+ * @len: data length
+ */
+int imanager_write_ram(struct imanager_ec_data *ec, int ram_type, u8 offset,
+ u8 *data, u8 len)
+{
+ int ret;
+
+ ret = imanager_check_ec_ready(&ec->io);
+ if (ret)
+ return ret;
+
+ ec->io.write(EC_MSG_OFFSET_PARAM, ram_type);
+ ec->io.write(EC_MSG_OFFSET_DATA, offset);
+ ec->io.write(EC_MSG_OFFSET_LEN, len);
+
+ data_to_ec(&ec->io, data, len, EC_MSG_OFFSET_RAM_DATA);
+
+ ec->io.write(EC_MSG_OFFSET_CMD, EC_CMD_RAM_WR);
+
+ ret = imanager_check_ec_ready(&ec->io);
+ if (ret)
+ return ret;
+
+ ret = ec->io.read(EC_MSG_OFFSET_STATUS);
+ if (ret != EC_F_SUCCESS)
+ return -EIO;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imanager_write_ram);
+
+/**
+ * imanager_read - read data through request/response messaging
+ * @ec: imanager_ec_data structure describing the EC
+ * @cmd: imanager EC firmware command
+ * @msg: imanager_ec_message structure holding the message
+ */
+int imanager_read(struct imanager_ec_data *ec, u8 cmd,
+ struct imanager_ec_message *msg)
+{
+ return imanager_msg_xfer(ec, cmd, msg, false);
+}
+EXPORT_SYMBOL_GPL(imanager_read);
+
+/**
+ * imanager_write - write data through request/response messaging
+ * @ec: imanager_ec_data structure describing the EC
+ * @cmd: imanager EC firmware command
+ * @msg: imanager_ec_message structure holding the message
+ */
+int imanager_write(struct imanager_ec_data *ec, u8 cmd,
+ struct imanager_ec_message *msg)
+{
+ return imanager_msg_xfer(ec, cmd, msg, true);
+}
+EXPORT_SYMBOL_GPL(imanager_write);
+
+/**
+ * imanager_read8 - read 8-bit data
+ * @ec: imanager_ec_data structure describing the EC
+ * @cmd: imanager EC firmware command
+ * @param: parameter depening on cmd - device ID, offset or unit number
+ */
+int imanager_read8(struct imanager_ec_data *ec, u8 cmd, u8 param)
+{
+ int ret;
+ struct imanager_ec_message msg = {
+ .rlen = 1,
+ .wlen = 0,
+ .param = param,
+ .data = NULL,
+ };
+
+ ret = imanager_read(ec, cmd, &msg);
+ if (ret)
+ return ret;
+
+ return msg.u.data[0];
+}
+EXPORT_SYMBOL_GPL(imanager_read8);
+
+/**
+ * imanager_read16 - read 16-bit data
+ * @ec: imanager_ec_data structure describing the EC
+ * @cmd: imanager EC firmware command
+ * @param: parameter depening on cmd - device ID, offset or unit number
+ */
+int imanager_read16(struct imanager_ec_data *ec, u8 cmd, u8 param)
+{
+ int ret;
+ struct imanager_ec_message msg = {
+ .rlen = 2,
+ .wlen = 0,
+ .param = param,
+ .data = NULL,
+ };
+
+ ret = imanager_read(ec, cmd, &msg);
+ if (ret)
+ return ret;
+
+ return (msg.u.data[0] << 8 | msg.u.data[1]);
+}
+EXPORT_SYMBOL_GPL(imanager_read16);
+
+/**
+ * imanager_write8 - write 8-bit data
+ * @ec: imanager_ec_data structure describing the EC
+ * @cmd: imanager EC firmware command
+ * @param: parameter depening on cmd - device ID, offset or unit number
+ * @byte: 8-bit data
+ */
+int imanager_write8(struct imanager_ec_data *ec, u8 cmd, u8 param, u8 byte)
+{
+ struct imanager_ec_message msg = {
+ .rlen = 0,
+ .wlen = 1,
+ .param = param,
+ .u = {
+ .data = { byte, 0 },
+ },
+ };
+
+ return imanager_write(ec, cmd, &msg);
+}
+EXPORT_SYMBOL_GPL(imanager_write8);
+
+/**
+ * imanager_write16 - write 16-bit data
+ * @ec: imanager_ec_data structure describing the EC
+ * @cmd: imanager EC firmware command
+ * @param: parameter depening on cmd - device ID, offset or unit number
+ * @word: 16-bit data
+ */
+int imanager_write16(struct imanager_ec_data *ec, u8 cmd, u8 param, u16 word)
+{
+ struct imanager_ec_message msg = {
+ .rlen = 0,
+ .wlen = 2,
+ .param = param,
+ .u = {
+ .data = { (word >> 8), (word & 0xff), 0 },
+ },
+ };
+
+ return imanager_write(ec, cmd, &msg);
+}
+EXPORT_SYMBOL_GPL(imanager_write16);
+
+enum imanager_cells {
+ IMANAGER_BACKLIGHT = 0,
+ IMANAGER_GPIO,
+ IMANAGER_HWMON,
+ IMANAGER_SMB,
+ IMANAGER_WDT,
+};
+
+/**
+ * iManager devices which are available via firmware.
+ */
+
+static const struct mfd_cell imanager_devs[] = {
+ [IMANAGER_BACKLIGHT] = {
+ .name = "imanager-backlight",
+ },
+ [IMANAGER_GPIO] = {
+ .name = "imanager-gpio",
+ },
+ [IMANAGER_HWMON] = {
+ .name = "imanager-hwmon",
+ },
+ [IMANAGER_SMB] = {
+ .name = "imanager-smbus",
+ },
+ [IMANAGER_WDT] = {
+ .name = "imanager-wdt",
+ },
+};
+
+static int imanager_register_cells(struct imanager_device_data *imgr)
+{
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct mfd_cell devs[ARRAY_SIZE(imanager_devs)];
+ int i = 0;
+
+ if (ec->features & IMANAGER_FEATURE_BACKLIGHT)
+ devs[i++] = imanager_devs[IMANAGER_BACKLIGHT];
+
+ if (ec->features & IMANAGER_FEATURE_GPIO)
+ devs[i++] = imanager_devs[IMANAGER_GPIO];
+
+ if (ec->features & IMANAGER_FEATURE_HWMON_ADC)
+ devs[i++] = imanager_devs[IMANAGER_HWMON];
+
+ if (ec->features & IMANAGER_FEATURE_SMBUS)
+ devs[i++] = imanager_devs[IMANAGER_SMB];
+
+ if (ec->features & IMANAGER_FEATURE_WDT)
+ devs[i++] = imanager_devs[IMANAGER_WDT];
+
+ return mfd_add_devices(imgr->dev, -1, devs, i, NULL, 0, NULL);
+}
+
+static struct resource imanager_ioresource = {
+ .start = IT8528_DAT_PORT,
+ .end = IT8518_DAT_PORT,
+ .flags = IORESOURCE_IO,
+};
+
+static ssize_t imanager_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_device_data *data = dev_get_drvdata(dev);
+ struct imanager_info *info = &data->ec.info;
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", info->version);
+}
+
+static ssize_t imanager_chip_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct imanager_device_data *data = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", data->ec.chip_name);
+}
+
+static DEVICE_ATTR(imanager_version, 0444, imanager_version_show, NULL);
+static DEVICE_ATTR(imanager_chip, 0444, imanager_chip_show, NULL);
+
+static struct attribute *imanager_attributes[] = {
+ &dev_attr_imanager_version.attr,
+ &dev_attr_imanager_chip.attr,
+ NULL
+};
+
+static const struct attribute_group imanager_attr_group = {
+ .attrs = imanager_attributes,
+};
+
+static int imanager_platform_create(void)
+{
+ int ret;
+
+ imanager_pdev = platform_device_alloc("imanager", -1);
+ if (!imanager_pdev)
+ return -ENOMEM;
+
+ /* No platform device data required */
+
+ ret = platform_device_add_resources(imanager_pdev,
+ &imanager_ioresource, 1);
+ if (ret)
+ goto err;
+
+ ret = platform_device_add(imanager_pdev);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ platform_device_put(imanager_pdev);
+ return ret;
+}
+
+static inline int ec_read_chipid(u16 addr)
+{
+ return (ec_inb(addr, CHIP_DEVID_MSB) << 8 |
+ ec_inb(addr, CHIP_DEVID_LSB));
+}
+
+static int imanager_detect_device(struct imanager_device_data *imgr)
+{
+ struct imanager_ec_data *ec = &imgr->ec;
+ struct device *dev = imgr->dev;
+ struct imanager_info *info = &imgr->ec.info;
+ int chipid = ec_read_chipid(EC_BASE_ADDR);
+ int ret;
+
+ if (chipid == CHIP_ID_IT8518) {
+ ec->io.read = ec_io18_read;
+ ec->io.write = ec_io18_write;
+ ec->chip_name = chip_names[IT8518];
+ } else if (chipid == CHIP_ID_IT8528) {
+ ec->io.read = ec_io28_read;
+ ec->io.write = ec_io28_write;
+ ec->chip_name = chip_names[IT8528];
+ }
+
+ ret = imanager_ec_init(ec);
+ if (ret) {
+ dev_err(dev, "iManager firmware communication error\n");
+ return ret;
+ }
+
+ dev_info(dev, "Found Advantech iManager %s: %s (%s)\n",
+ ec->chip_name, info->version, info->type);
+
+ ret = sysfs_create_group(&dev->kobj, &imanager_attr_group);
+ if (ret)
+ return ret;
+
+ ret = imanager_register_cells(imgr);
+ if (ret)
+ sysfs_remove_group(&dev->kobj, &imanager_attr_group);
+
+ return ret;
+}
+
+static int imanager_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imanager_device_data *imgr;
+
+ imgr = devm_kzalloc(dev, sizeof(*imgr), GFP_KERNEL);
+ if (!imgr)
+ return -ENOMEM;
+
+ imgr->dev = dev;
+ mutex_init(&imgr->lock);
+
+ platform_set_drvdata(pdev, imgr);
+
+ return imanager_detect_device(imgr);
+}
+
+static int imanager_remove(struct platform_device *pdev)
+{
+ sysfs_remove_group(&pdev->dev.kobj, &imanager_attr_group);
+ mfd_remove_devices(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver imanager_driver = {
+ .driver = {
+ .name = "imanager",
+ },
+ .probe = imanager_probe,
+ .remove = imanager_remove,
+};
+
+static int __init imanager_init(void)
+{
+ int chipid = ec_read_chipid(EC_BASE_ADDR);
+ int ret;
+
+ /* Check for the presence of the EC chip */
+ if ((chipid != CHIP_ID_IT8518) && (chipid != CHIP_ID_IT8528))
+ return -ENODEV;
+
+ ret = imanager_platform_create();
+ if (ret)
+ return ret;
+
+ return platform_driver_register(&imanager_driver);
+}
+
+static void __exit imanager_exit(void)
+{
+ if (imanager_pdev)
+ platform_device_unregister(imanager_pdev);
+
+ platform_driver_unregister(&imanager_driver);
+}
+
+module_init(imanager_init);
+module_exit(imanager_exit);
+
+MODULE_DESCRIPTION("Advantech iManager Core Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-core");
diff --git a/include/linux/mfd/imanager-ec.h b/include/linux/mfd/imanager-ec.h
new file mode 100644
index 0000000..6319b7a
--- /dev/null
+++ b/include/linux/mfd/imanager-ec.h
@@ -0,0 +1,228 @@
+/*
+ * Advantech iManager - firmware interface
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.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.
+ */
+
+#ifndef _LINUX_MFD_IMANAGER_EC_H_
+#define _LINUX_MFD_IMANAGER_EC_H_
+
+#include <linux/types.h>
+
+/* Delay time for port polling in micro seconds */
+#define EC_DELAY_MIN 200UL
+#define EC_DELAY_MAX 250UL
+
+#define EC_MAX_RETRY 400UL
+
+#define CHIP_ID_IT8518 0x8518
+#define CHIP_ID_IT8528 0x8528
+
+#define EC_BASE_ADDR 0x029C
+
+#define IT8528_CMD_PORT 0x029A
+#define IT8528_DAT_PORT 0x0299
+#define IT8518_CMD_PORT 0x029E
+#define IT8518_DAT_PORT 0x029F
+
+/* 16-bit device ID registers */
+#define CHIP_DEVID_MSB 0x20
+#define CHIP_DEVID_LSB 0x21
+
+#define EC_MAX_GPIO_NUM 8UL
+#define EC_MAX_ADC_NUM 5UL
+#define EC_MAX_FAN_NUM 3UL
+#define EC_MAX_BLC_NUM 2UL
+#define EC_MAX_SMB_NUM 4UL
+#define EC_MAX_WDT_NUM 2UL
+
+#define EC_PAYLOAD_SIZE 40UL
+#define EC_MSG_SIZE sizeof(struct imanager_ec_smb_message)
+#define EC_MSG_HDR_SIZE sizeof(struct imanager_ec_smb_msg_hdr)
+
+#define EC_MAX_DID 32UL
+
+/*
+ * iManager commands
+ */
+#define EC_CMD_CHK_RDY 0UL
+#define EC_CMD_HWP_RD 0x11UL
+#define EC_CMD_HWP_WR 0x12UL
+#define EC_CMD_GPIO_DIR_RD 0x30UL
+#define EC_CMD_GPIO_DIR_WR 0x31UL
+#define EC_CMD_PWM_FREQ_RD 0x36UL
+#define EC_CMD_PWM_FREQ_WR 0x32UL
+#define EC_CMD_PWM_POL_RD 0x37UL
+#define EC_CMD_PWM_POL_WR 0x33UL
+#define EC_CMD_SMB_FREQ_RD 0x34UL
+#define EC_CMD_SMB_FREQ_WR 0x35UL
+#define EC_CMD_FAN_CTL_RD 0x40UL
+#define EC_CMD_FAN_CTL_WR 0x41UL
+#define EC_CMD_THZ_RD 0x42UL
+#define EC_CMD_DEV_TBL_RD 0x20UL
+#define EC_CMD_FW_INFO_RD 0xF0UL
+#define EC_CMD_BUF_CLR 0xC0UL
+#define EC_CMD_BUF_RD 0xC1UL
+#define EC_CMD_BUF_WR 0xC2UL
+#define EC_CMD_RAM_RD 0x1EUL
+#define EC_CMD_RAM_WR 0x1FUL
+#define EC_CMD_I2C_RW 0x0EUL
+#define EC_CMD_I2C_WR 0x0FUL
+#define EC_CMD_WDT_CTRL 0x28UL
+
+/*
+ * ACPI RAM offsets
+ */
+#define EC_OFFSET_FAN_ALERT 0x6FUL
+#define EC_OFFSET_FAN_ALERT_LIMIT 0x76UL
+#define EC_OFFSET_BRIGHTNESS1 0x50UL
+#define EC_OFFSET_BRIGHTNESS2 0x52UL
+#define EC_OFFSET_BACKLIGHT_CTRL 0x99UL
+#define EC_OFFSET_FW_RELEASE 0xF8UL
+
+/* iManager flags */
+#define IMANAGER_FEATURE_BACKLIGHT BIT(0)
+#define IMANAGER_FEATURE_GPIO BIT(1)
+#define IMANAGER_FEATURE_HWMON_ADC BIT(2)
+#define IMANAGER_FEATURE_HWMON_FAN BIT(3)
+#define IMANAGER_FEATURE_SMBUS BIT(4)
+#define IMANAGER_FEATURE_WDT BIT(5)
+
+#define EC_IO28_OUTBUF BIT(0)
+#define EC_IO28_INBUF BIT(1)
+
+#define EC_F_SUCCESS BIT(0)
+#define EC_F_CMD_COMPLETE BIT(7)
+#define EC_F_HWMON_MSG BIT(9)
+
+/* iManager offsets */
+#define EC_MSG_OFFSET(N) (0UL + (N))
+#define EC_MSG_OFFSET_CMD EC_MSG_OFFSET(0)
+#define EC_MSG_OFFSET_STATUS EC_MSG_OFFSET(1)
+#define EC_MSG_OFFSET_PARAM EC_MSG_OFFSET(2)
+#define EC_MSG_OFFSET_DATA EC_MSG_OFFSET(3)
+#define EC_MSG_OFFSET_RAM_DATA EC_MSG_OFFSET(4)
+#define EC_MSG_OFFSET_PAYLOAD EC_MSG_OFFSET(7)
+#define EC_MSG_OFFSET_LEN EC_MSG_OFFSET(0x2F)
+
+/* IT8528 based firmware require a read/write command offset. */
+#define EC_CMD_OFFSET_READ 0xA0UL
+#define EC_CMD_OFFSET_WRITE 0x50UL
+
+#define EC_KERNEL_MINOR(x) ((x) & 0xff)
+#define EC_KERNEL_MAJOR(x) ({ typeof(x) __x = (x >> 8); \
+ ((__x >> 4) * 10 + (__x & 0x0f)); })
+#define EC_FIRMWARE_MINOR(x) EC_KERNEL_MINOR(x)
+#define EC_FIRMWARE_MAJOR(x) EC_KERNEL_MAJOR(x)
+#define EC_PROJECT_CODE(x) EC_KERNEL_MINOR(x)
+
+enum imanager_smb_cells { SMB_EEP = 0, I2C_OEM, SMB_1, SMB_PECI };
+
+enum imanager_device_type { ADC = 1, DAC, GPIO, IRQ, PWM, SMB };
+
+enum imanager_device_id {
+ /* GPIO */
+ GPIO0 = 0x10, GPIO1, GPIO2, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7,
+ /* FAN */
+ CPUFAN_2P = 0x20, CPUFAN_4P, SYSFAN1_2P, SYSFAN1_4P, SYSFAN2_2P,
+ SYSFAN2_4P,
+ /* Brightness Control */
+ BRIGHTNESS = 0x26, BRIGHTNESS2 = 0x88,
+ /* SMBus */
+ SMBOEM0 = 0x28, SMBOEM1, SMBOEM2, SMBEEPROM,
+ SMBTHM0 = 0x2C, SMBTHM1, SMBSECEEP, I2COEM,
+ SMBEEP2K = 0x38, OEMEEP, OEMEEP2K, SMBPECI,
+ /* ADC */
+ CMOSBAT = 0x50, CMOSBAT_2, CMOSBAT_10,
+ ADC5VS0 = 0x56, ADC5VS0_2, ADC5VS0_10,
+ ADC5VS5 = 0x59, ADC5VS5_2, ADC5VS5_10,
+ ADC33VS0 = 0x5C, ADC33VS0_2, ADC33VS0_10,
+ ADC33VS5 = 0x5F, ADC33VS5_2, ADC33VS5_10,
+ ADC12VS0 = 0x62, ADC12VS0_2, ADC12VS0_10,
+ VCOREA = 0x65, VCOREA_2, VCOREA_10,
+ CURRENT = 0x74,
+ /* Watchdog */
+ WDIRQ = 0x78, WDNMI,
+};
+
+/**
+ * struct imanager_ec_device - Describes iManager EC Device
+ * @did: iManager Device ID
+ * @type: iManager Device Type
+ * @scale: Scaling factor
+ */
+struct imanager_ec_device {
+ unsigned int did;
+ unsigned int type;
+ unsigned int scale;
+};
+
+/**
+ * IMANAGER_EC_DEVICE - macro used to describe a specific iManager device
+ * @device_id: the 8 bit iManager device ID
+ * @device_type: the iManager device type
+ * @scaling_factor: the iManager sensor device scaling factor
+ *
+ * This macro is used to create a struct imanager_ec_device that matches a
+ * specific iManager device
+ */
+#define IMANAGER_EC_DEVICE(device_id, device_type, scaling_factor) \
+ .did = (device_id), .type = (device_type), .scale = (scaling_factor)
+
+/**
+ * struct imanager_io_ops - iManager I/O operation structure
+ * @read: iManager read call-back
+ * @write: iManager write call-back
+ */
+struct imanager_io_ops {
+ int (*read)(int cmd);
+ int (*write)(int cmd, int value);
+};
+
+/**
+ * struct imanager_ec_smb_msg_hdr - Defines iManager EC SMBus message header
+ * @addr_low: low-byte of word address (or data)
+ * @addr_high: high-byte of word address (or data)
+ * @rlen: SMB read length
+ * @wlen: SMB write length
+ * @cmd: SMB command
+ */
+struct imanager_ec_smb_msg_hdr {
+ unsigned char addr_low;
+ unsigned char addr_high;
+ unsigned char rlen;
+ unsigned char wlen;
+ unsigned char cmd;
+} __attribute__((__packed__));
+
+/**
+ * struct imanager_ec_smb_message - Defines iManager SMBus message
+ * @hdr: iManager SMBus message header
+ * @data: iManager SMBus message data field (payload)
+ */
+struct imanager_ec_smb_message {
+ struct imanager_ec_smb_msg_hdr hdr;
+ unsigned char data[EC_PAYLOAD_SIZE];
+} __attribute__((__packed__));
+
+/**
+ * struct imanager_ec_version - Defines iManager EC firmware version structure
+ * @kernel: iManager EC FW kernel release
+ * @chipid: iManager EC chip ID
+ * @project_code: iManager EC FW status
+ * @firmware: iManager EC FW release
+ */
+struct imanager_ec_version {
+ unsigned short kernel;
+ unsigned short chipid;
+ unsigned short project_code;
+ unsigned short firmware;
+} __attribute__((__packed__));
+
+#endif
diff --git a/include/linux/mfd/imanager.h b/include/linux/mfd/imanager.h
new file mode 100644
index 0000000..32d7af6
--- /dev/null
+++ b/include/linux/mfd/imanager.h
@@ -0,0 +1,221 @@
+/*
+ * Advantech iManager MFD
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.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.
+ */
+
+#ifndef _LINUX_MFD_IMANAGER_H_
+#define _LINUX_MFD_IMANAGER_H_
+
+#include <linux/mutex.h>
+#include <linux/mfd/imanager-ec.h>
+
+#define IMANAGER_PCB_NAME_LEN 9
+#define IMANAGER_VERSION_LEN 40
+
+/**
+ * IMANAGER_MSG_SIMPLE - macro used to describe a simple iManager message
+ * @read_len: the message read length
+ * @write_len: the message write length
+ * @parameter: the message parameter
+ * @_data: pointer to data field
+ *
+ * This macro is used to create a struct imanager_ec_message used for basic
+ * EC communication
+ */
+#define IMANAGER_MSG_SIMPLE(read_len, write_len, parameter, _data) \
+ .rlen = (read_len), .wlen = (write_len), \
+ .param = (parameter), .data = (_data)
+
+/**
+ * struct imanager_ec_message - Describes iManager EC message
+ * @rlen: iManager message read length
+ * @wlen: iManager message write length
+ * @param: iManager message parameter (offset, id, or unit number)
+ * @u: union holding struct imanager_ec_smb_message and data field
+ * @data: pointer to data field
+ */
+struct imanager_ec_message {
+ unsigned int rlen;
+ unsigned int wlen;
+ unsigned int param;
+ union {
+ struct imanager_ec_smb_message smb;
+ unsigned char data[EC_MSG_SIZE];
+ } u;
+
+ unsigned char *data;
+};
+
+/**
+ * struct imanager_device_attribute - Describes iManager Device attribute
+ * @did: iManager Device ID
+ * @hwp: iManager Hardware Pin number
+ * @pol: iManager Device Polarity
+ * @ecdev: pointer to iManager device table entry
+ */
+struct imanager_device_attribute {
+ unsigned int did;
+ unsigned int hwp;
+ unsigned int pol;
+ const struct imanager_ec_device *ecdev;
+};
+
+/**
+ * struct imanager_gpio_device - Describes iManager GPIO device
+ * @num: available GPIO pins
+ * @attr: pointer to array of iManager GPIO device attribute
+ */
+struct imanager_gpio_device {
+ unsigned int num;
+ struct imanager_device_attribute *attr[EC_MAX_GPIO_NUM];
+};
+
+/**
+ * struct imanager_adc_device - Describes iManager ADC device
+ * @num: available ADC devices
+ * @attr: pointer to array of iManager ADC device attribute
+ * @label pointer to ADC label
+ */
+struct imanager_adc_device {
+ unsigned int num;
+ struct imanager_device_attribute *attr[EC_MAX_ADC_NUM];
+ const char *label[EC_MAX_ADC_NUM];
+};
+
+/**
+ * struct imanager_fan_device - Describes iManager FAN device
+ * @num: available FAN devices
+ * @attr: pointer to array of iManager FAN device attribute
+ * @label pointer to FAN label
+ * @temp_label pointer to FAN temperature label
+ */
+struct imanager_fan_device {
+ unsigned int num;
+ struct imanager_device_attribute *attr[EC_MAX_FAN_NUM];
+ const char *label[EC_MAX_FAN_NUM];
+ const char *temp_label[EC_MAX_FAN_NUM];
+};
+
+/**
+ * struct imanager_hwmon_device - Describes iManager hwmon device
+ * @adc: iManager ADC device
+ * @fan: iManager FAN device
+ */
+struct imanager_hwmon_device {
+ struct imanager_adc_device adc;
+ struct imanager_fan_device fan;
+};
+
+/**
+ * struct imanager_i2c_device - Describes iManager I2C device
+ * @num: available I2C devices
+ * @attr: pointer to array of iManager GPIO device attribute
+ */
+struct imanager_i2c_device {
+ unsigned int num;
+ struct imanager_device_attribute *attr[EC_MAX_SMB_NUM];
+};
+
+/**
+ * struct imanager_backlight_device - Describes iManager backlight device
+ * @num: available backlight devices
+ * @attr: pointer to array of iManager backlight device attribute
+ * @brightnes: array of brightness devices
+ */
+struct imanager_backlight_device {
+ unsigned int num;
+ struct imanager_device_attribute *attr[EC_MAX_BLC_NUM];
+ unsigned char brightness[EC_MAX_BLC_NUM];
+};
+
+/**
+ * struct imanager_watchdog_device - Describes iManager watchdog device
+ * @num: available WD devices
+ * @attr: pointer to array of iManager watchdog device attribute
+ */
+struct imanager_watchdog_device {
+ unsigned int num;
+ struct imanager_device_attribute *attr[EC_MAX_BLC_NUM];
+};
+
+/**
+ * struct imanager_info - iManager device information structure
+ * @kernel_major: iManager EC kernel major revision
+ * @kernel_minor: iManager EC kernel minor revision
+ * @firmware_major: iManager EC firmware major revision
+ * @firmware_minor: iManager EC firmware minor revision
+ * @type: iManager type - release/debug/custom
+ * @pcb_name: PC board name
+ * @version: iManager version string
+ */
+struct imanager_info {
+ unsigned int kernel_major;
+ unsigned int kernel_minor;
+ unsigned int firmware_major;
+ unsigned int firmware_minor;
+ const char *type;
+ char version[IMANAGER_VERSION_LEN];
+};
+
+/**
+ * struct imanager_ec_data - iManager EC data structure
+ * @features: iManager feature mask
+ * @attr: array of iManager device attribute structure
+ * @io: imanager_io_ops structure providing I/O operations
+ * @gpio: iManager GPIO device structure
+ * @hwmon: iManager Hardware monitor device structure
+ * @i2c: iManager I2C/SMBus device structure
+ * @bl: iManager Backlight/Brightness device structure
+ * @wdt: iManager Watchdog device structure
+ */
+struct imanager_ec_data {
+ unsigned int features;
+ const char *chip_name;
+ struct imanager_device_attribute attr[EC_MAX_DID];
+ struct imanager_io_ops io;
+ struct imanager_gpio_device gpio;
+ struct imanager_hwmon_device hwmon;
+ struct imanager_i2c_device i2c;
+ struct imanager_backlight_device bl;
+ struct imanager_watchdog_device wdt;
+ struct imanager_info info;
+};
+
+/**
+ * struct imanager_device_data - Internal representation of the iManager device
+ * @ec: iManager data structure describing the EC
+ * @dev: Pointer to kernel device structure
+ * @lock: iManager mutex
+ */
+struct imanager_device_data {
+ struct imanager_ec_data ec;
+ struct device *dev;
+ struct mutex lock; /* generic mutex for imanager core */
+};
+
+enum ec_ram_type { EC_RAM_ACPI = 1, EC_RAM_HW, EC_RAM_EXT };
+
+int imanager_read(struct imanager_ec_data *ec, u8 cmd,
+ struct imanager_ec_message *msg);
+int imanager_write(struct imanager_ec_data *ec, u8 cmd,
+ struct imanager_ec_message *msg);
+
+int imanager_read8(struct imanager_ec_data *ec, u8 cmd, u8 param);
+int imanager_write8(struct imanager_ec_data *ec, u8 cmd, u8 param, u8 byte);
+
+int imanager_read16(struct imanager_ec_data *ec, u8 cmd, u8 param);
+int imanager_write16(struct imanager_ec_data *ec, u8 cmd, u8 param, u16 word);
+
+int imanager_read_ram(struct imanager_ec_data *ec, int ram_type, u8 offset,
+ u8 *buf, u8 len);
+int imanager_write_ram(struct imanager_ec_data *ec, int ram_type, u8 offset,
+ u8 *data, u8 size);
+
+#endif
--
2.10.1
^ permalink raw reply related
* [PATCH v4 0/6] Advantech iManager EC driver set
From: Richard Vidal-Dorsch @ 2016-11-02 8:37 UTC (permalink / raw)
To: linus.walleij, gnurou, jdelvare, linux, wsa, lee.jones,
jingoohan1, tomi.valkeinen, wim, linux-kernel, linux-gpio,
linux-hwmon, linux-i2c, linux-fbdev, linux-watchdog, k.kozlowski
Cc: Richard Vidal-Dorsch, jo.sunga, weilun.huang
The Advantech iManager is a custom embedded controller based on ITE IT8518
or IT8528 EC (depending on PCB). It runs a custom firmware that provides
access to features such as GPIO, I2C/SMbus, hwmon, watchdog, and
backlight/brightness control. All drivers are being managed by the
iManager (mfd) core driver. It acts as a 'gateway' and handles
communications between EC and sub-drivers. The imanager-core follows a
similar concept as Kontron's kempld-core driver.
During core init, the device id list is retrieved from the firmware and an
internal device list is being created. This list is then passed down to
managed drivers.
The out-of-tree iManager driver set is maintained at
https://www.github.com/rvido/iManager
Notebook manufactures such as Dell (XPS series) or Razerzone (Razer Blade)
are using similar ITE ECs or even the same ones to implement their own
embedded functions. Due to the level of firmware customization which those
ITE ECs provide, the iManager EC driver set is incompatible with those
vendors solutions (and probably others too).
Note that v1..v3 were submitted in Jan. 2016. It took some time to apply
suggested changes. However, I kept the original versioning to avoid conflict
with previous patch submissions.
Thanks to Krzysztof Kozłowski, Guenter Roeck, and Lee Jones for their
invaluable feedback. Those drivers clearly needed to be further improved
and cleaned.
Changes from v3:
- Merge imanager-ec-* with corresponding drivers
This eliminates static init vars and reduces code size
- Remove Documentation/devicetree/bindings/mfd/imanager.txt
No support for DT
- Add API comments
- List include files in alphabetic order
- Run patches through ./scripts/checkpatch.pl --strict
- Remove headers except imanger.h/imanager-ec.h
Store them in include/linux/mfd/
imanager-core:
- Reduce amount of exported functions in core
- Use usleep_range() instead of udelay()
- Use dev_info() or dev_warn() where necessary
- Fix register/probing and __init/__exit
- Optimize imanager_read_device_config()
- Shrink down EC device table to known and supported devices
- Define flags with BIT()
- Use new imanager device struct
i2c-imanager:
- Add support for multiple I2C/SMbus adapters
- Use new imanager device struct
imanager-hwmon-*
gpio-imanager:
imanager_bl:
imanager_wdt:
- Define flags with BIT()
- Use new imanager device struct
Changes from v2:
- Remove .owner from platform_driver in:
drivers/gpio/imanager-bl
drivers/gpio/imanager-core.c
drivers/gpio/imanager-i2c.c
drivers/gpio/imanager-hwmon.c
Changes from v1:
- Remove .owner from platform_driver in drivers/gpio/imanager-gpio.c
- Remove .owner from platform_driver in drivers/gpio/imanager-wdt.c
- Replace 2015 by 2016 in all files
Richard Vidal-Dorsch (6):
Add Advantech iManager MFD core driver
Add Advantech iManager GPIO driver
Add Advantech iManager HWmon driver
Add Advantech iManager I2C driver
Add Advantech iManager Backlight driver
Add Advantech iManager Watchdog driver
drivers/gpio/Kconfig | 10 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-imanager.c | 155 +++++
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/imanager-hwmon.c | 1226 +++++++++++++++++++++++++++++++++
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-imanager.c | 461 +++++++++++++
drivers/mfd/Kconfig | 18 +
drivers/mfd/Makefile | 1 +
drivers/mfd/imanager-core.c | 941 +++++++++++++++++++++++++
drivers/video/backlight/Kconfig | 8 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/imanager_bl.c | 210 ++++++
drivers/watchdog/Kconfig | 11 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/imanager_wdt.c | 303 ++++++++
include/linux/mfd/imanager-ec.h | 228 ++++++
include/linux/mfd/imanager.h | 221 ++++++
20 files changed, 3819 insertions(+)
create mode 100644 drivers/gpio/gpio-imanager.c
create mode 100644 drivers/hwmon/imanager-hwmon.c
create mode 100644 drivers/i2c/busses/i2c-imanager.c
create mode 100644 drivers/mfd/imanager-core.c
create mode 100644 drivers/video/backlight/imanager_bl.c
create mode 100644 drivers/watchdog/imanager_wdt.c
create mode 100644 include/linux/mfd/imanager-ec.h
create mode 100644 include/linux/mfd/imanager.h
--
2.10.1
^ permalink raw reply
* Re: [PATCHv6 08/11] i2c: match vendorless strings on the internal string length
From: Lee Jones @ 2016-11-01 7:22 UTC (permalink / raw)
To: Kieran Bingham
Cc: Peter Rosin, Wolfram Sang, linux-i2c, linux-kernel,
Javier Martinez Canillas, sameo
In-Reply-To: <51902bcc-f5e6-ff9a-9aeb-bf7b8deeaf26@bingham.xyz>
On Mon, 31 Oct 2016, Kieran Bingham wrote:
> On 31/10/16 13:55, Peter Rosin wrote:
> > On 2016-10-26 10:53, Lee Jones wrote:
> >> On Tue, 25 Oct 2016, Kieran Bingham wrote:
> >>
> >>> If a user provides a shortened string to match a device to the sysfs i2c
> >>> interface it will match on the first string that contains that string
> >>> prefix.
> >>>
> >>> for example:
> >>> echo a 0x68 > /sys/bus/i2c/devices/i2c-2/new_device
> >>>
> >>> will match as3711, as3722, and ak8975 incorrectly.
> >>>
> >>> Signed-off-by: Kieran Bingham <kieran@bingham.xyz>
> >>
> >> Acked-by: Lee Jones <lee.jones@linaro.org>
> >>
> >>> ---
> >>> drivers/i2c/i2c-core.c | 2 +-
> >>> 1 file changed, 1 insertion(+), 1 deletion(-)
> >>>
> >>> diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
> >>> index 01bce56f733a..50c9cfdb87b7 100644
> >>> --- a/drivers/i2c/i2c-core.c
> >>> +++ b/drivers/i2c/i2c-core.c
> >>> @@ -1708,7 +1708,7 @@ i2c_of_match_device_strip_vendor(const struct of_device_id *matches,
> >>> else
> >>> name++;
> >>>
> >>> - if (!strncasecmp(client->name, name, strlen(client->name)))
> >>> + if (!strncasecmp(client->name, name, strlen(name)))
> >>> return matches;
> >>> }
> >>>
> >>
> >
> > Is that really so much better?
>
> My original thought was that it verifies 'more' of the userspace input.
> but...
>
> > With this patch
> > echo as3711CRAP 0x68 > /sys/...
> > will match as3711.
> >
> > What if there is some as37112 driver that is the real target?
>
> You're right - It looks like the only way to do this correctly is to
> match the strncasecmp and the strlen of both strings.
>
> So really we should be using sysfs_streq(). The only limitation there is
> that this original code was performing a case-insensitive compare.
>
> Lee - Where did the requirement for case insensitive matching come from
> in your original code. Is it expected to be case-insensitive from the
> I2C sysfs interface? or are dt-nodes expected to be case-sensitive?
Compatible strings are always lower-case.
> Does anyone see reason that this shouldn't be using sysfs_streq()? or do
> we need a sysfs_strcaseeq()...
... but I don't see an issue with not being case sensitive. Certainly
if it makes the logic easier/more consistent.
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* Re: [RFC v2 2/2] i2c: Pass i2c_device_id to probe func when using DT ids through ACPI
From: Phong Vo @ 2016-11-01 6:14 UTC (permalink / raw)
To: mika.westerberg, leonard.crestez
Cc: wsa+renesas, rafael.j.wysocki, len.brown, irina.tirdea,
octavian.purdila, daniel.baluta, linux-kernel, linux-acpi,
linux-i2c, Tin Huynh, Loc Ho, patches
>From: Mika Westerberg <mika.westerberg@linux.intel.com>
>Subject: Re: [RFC v2 2/2] i2c: Pass i2c_device_id to probe func when
using DT ids through ACPI
>Date: Monday 13th June 2016 09:26:55 UTC (5 months ago)
>
>On Fri, Jun 10, 2016 at 06:57:36PM +0300, Crestez Dan Leonard wrote:
>> On 06/10/2016 09:32 AM, Mika Westerberg wrote:
>> > On Thu, Jun 09, 2016 at 04:06:03PM +0300, Crestez Dan Leonard wrote:
>> >> When devices are instatiated through devicetree the i2c_client->name
is
>> >> set to the compatible string with company name stripped out. This is
>> >> then matched to the i2c_device_id table to pass the device_id to the
>> >> probe function. This id parameter is used by some device drivers to
>> >> differentiate between model numbers.
>> >>
>> >> When using ACPI this id parameter is NULL and the driver usually
needs
>> >> to do ACPI-specific differentiation.
>> >>
>> >> This patch attempts to find a valid i2c_device_id when using ACPI
with
>> >> DT-like compatible strings.
>> >
>> > So I don't really understand why it would be good idea to pass
>> > i2c_device_id for devices which are matched against their ACPI/DT
>> > tables. Apparently DT is already doing that so maybe there is some
>> > reason.
>> >
>> > Anyway, why not fill in the device name when it is first enumerated
>> > if it uses DT compatible property? Just like DT does.
>> >
>> This automatic matching of i2c_device_id works for devicetree because
>> of_i2c_register_device sets i2c_board_info.type to the compatible
string
>> with the vendor prefix removed. For I2C devices described via ACPI the
>> i2c_board_info.type string is set to the ACPI device name. This ends up
>> something like "PRP0001:00".
>>
>> This could be changed in acpi_i2c_get_info to use the of_compatible
>> string from DSD if present. Is that what you mean? That would work and
>> it would be cleaner than my patch. Something like this:
>>
>> diff --git drivers/i2c/i2c-core.c drivers/i2c/i2c-core.c
>> index 1e0ef9b..ba2fe7f 100644
>> --- drivers/i2c/i2c-core.c
>> +++ drivers/i2c/i2c-core.c
>> @@ -181,7 +181,24 @@ static int acpi_i2c_get_info(struct acpi_device
>*adev,
>>
>> acpi_dev_free_resource_list(&resource_list);
>>
>> - strlcpy(info->type, dev_name(&adev->dev), sizeof(info->type));
>> + /*
>> + * If we have a DT id set info.type to the first compatible
>> string with
>> + * the vendor prefix stripped. This is similar to
>of_modalias_node
>> + */
>> + if (adev->data.of_compatible) {
>> + const union acpi_object *obj;
>> + const char *str, *chr;
>> +
>> + obj = adev->data.of_compatible;
>> + if (obj->type == ACPI_TYPE_PACKAGE)
>> + obj = obj->package.elements;
>> + str = obj->string.pointer;
>> + chr = strchr(str, ',');
>> + if (chr)
>> + str = chr + 1;
>> + strlcpy(info->type, str, sizeof(info->type));
>> + } else
>> + strlcpy(info->type, dev_name(&adev->dev),
>> sizeof(info->type));
>>
>> return 0;
>> }>
>
>Yes, that's what I mean.
>
>> The biggest concern is that this would change the i2c device name
>> between kernel versions. Is that acceptable?
>
>I don't think that is a problem since I still have not seen a single
>system using ACPI _DSD so I would not expect anything to break.
>
>However, I'm still not convinced it is good idea to return i2c_device_id
>from a completely different table if we match using ACPI/DT table.
All,
Is there a conclusion on this? We have been tackling the same issue and
incidentally arrived at a
similar solution as like Lenard proposed in the patch above.
^ permalink raw reply
* [PATCH] i2c: core: fix NULL pointer dereference under race condition
From: Vladimir Zapolskiy @ 2016-10-31 19:46 UTC (permalink / raw)
To: Wolfram Sang, Peter Rosin; +Cc: linux-i2c
Race condition between registering an I2C device driver and
deregistering an I2C adapter device which is assumed to manage that
I2C device may lead to a NULL pointer dereference due to the
uninitialized list head of driver clients.
The root cause of the issue is that the I2C bus may know about the
registered device driver and thus it is matched by bus_for_each_drv(),
but the list of clients is not initialized and commonly it is NULL,
because I2C device drivers define struct i2c_driver as static and
clients field is expected to be initialized by I2C core:
i2c_register_driver() i2c_del_adapter()
driver_register() ...
bus_add_driver() ...
... bus_for_each_drv(..., __process_removed_adapter)
... i2c_do_del_adapter()
... list_for_each_entry_safe(..., &driver->clients, ...)
INIT_LIST_HEAD(&driver->clients);
To solve the problem it is sufficient to do clients list head
initialization before calling driver_register().
The problem was found while using an I2C device driver with a sluggish
registration routine on a bus provided by a physically detachable I2C
master controller, but practically the oops may be reproduced under
the race between arbitraty I2C device driver registration and managing
I2C bus device removal e.g. by unbinding the latter over sysfs:
% echo 21a4000.i2c > /sys/bus/platform/drivers/imx-i2c/unbind
Unable to handle kernel NULL pointer dereference at virtual address 00000000
Internal error: Oops: 17 [#1] SMP ARM
CPU: 2 PID: 533 Comm: sh Not tainted 4.9.0-rc3+ #61
Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)
task: e5ada400 task.stack: e4936000
PC is at i2c_do_del_adapter+0x20/0xcc
LR is at __process_removed_adapter+0x14/0x1c
Flags: NzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none
Control: 10c5387d Table: 35bd004a DAC: 00000051
Process sh (pid: 533, stack limit = 0xe4936210)
Stack: (0xe4937d28 to 0xe4938000)
Backtrace:
[<c0667be0>] (i2c_do_del_adapter) from [<c0667cc0>] (__process_removed_adapter+0x14/0x1c)
[<c0667cac>] (__process_removed_adapter) from [<c0516998>] (bus_for_each_drv+0x6c/0xa0)
[<c051692c>] (bus_for_each_drv) from [<c06685ec>] (i2c_del_adapter+0xbc/0x284)
[<c0668530>] (i2c_del_adapter) from [<bf0110ec>] (i2c_imx_remove+0x44/0x164 [i2c_imx])
[<bf0110a8>] (i2c_imx_remove [i2c_imx]) from [<c051a838>] (platform_drv_remove+0x2c/0x44)
[<c051a80c>] (platform_drv_remove) from [<c05183d8>] (__device_release_driver+0x90/0x12c)
[<c0518348>] (__device_release_driver) from [<c051849c>] (device_release_driver+0x28/0x34)
[<c0518474>] (device_release_driver) from [<c0517150>] (unbind_store+0x80/0x104)
[<c05170d0>] (unbind_store) from [<c0516520>] (drv_attr_store+0x28/0x34)
[<c05164f8>] (drv_attr_store) from [<c0298acc>] (sysfs_kf_write+0x50/0x54)
[<c0298a7c>] (sysfs_kf_write) from [<c029801c>] (kernfs_fop_write+0x100/0x214)
[<c0297f1c>] (kernfs_fop_write) from [<c0220130>] (__vfs_write+0x34/0x120)
[<c02200fc>] (__vfs_write) from [<c0221088>] (vfs_write+0xa8/0x170)
[<c0220fe0>] (vfs_write) from [<c0221e74>] (SyS_write+0x4c/0xa8)
[<c0221e28>] (SyS_write) from [<c0108a20>] (ret_fast_syscall+0x0/0x1c)
Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
---
drivers/i2c/i2c-core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
index 1704fc84d647..b432b64e307a 100644
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -2179,6 +2179,7 @@ int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
+ INIT_LIST_HEAD(&driver->clients);
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
@@ -2189,7 +2190,6 @@ int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
pr_debug("driver [%s] registered\n", driver->driver.name);
- INIT_LIST_HEAD(&driver->clients);
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
--
2.8.1
^ permalink raw reply related
* Re: [PATCH] i2c: imx: add slave support. v2 status
From: Maxim Syrchin @ 2016-10-31 18:21 UTC (permalink / raw)
To: Frkuska, Joshua, linux-i2c
Cc: wsa, peda, Jiada_Wang, linux-kernel, Zapolskiy, Vladimir,
Baxter, Jim
In-Reply-To: <0f7d5f1c-5889-27f3-f4e0-fc79960b8bf2@mentor.com>
Hello,
Please find some comments below.
31.10.2016 5:14, Frkuska, Joshua пишет:
> Hello Maxim,
>
> Thank you very much for the intermediate patch. I am in the process of
> reviewing it. Please let me clarify a few questions I have.
>
> 1. What alternative to "bus busy/bus free/IBB" polling do you have in
> mind? This seems like a reasonable approach to me.
We didn't find any suitable alternatives. The only one we're considered
was using timeout on receive (which is kind of polling of course)
> 2. What are the major points you consider in need of refactoring?
As you can see we have implemented FSM in slave thread.
- Due to lack of time all master functionality had not been included in
State Machine.
- wait_event_timeout() calls are used in every event handler (obviosly
it is better to have only one wait function)
- Need to review state switching code
> 3. You mention race conditions being fixed in this version relating to
> bus-locking by the slave and breaking slave transactions by the
> master. Does this mean mixed slave/master mode works to your
> satisfaction? If not, what work still needs to be done here?
Yes mixed slave/master mode works ok. It had passed long-lasting stress
tests (async message exchange of two imx6 boards connected together by
i2c bus )
> 4. You mention the need for a slave locking test and a work-around
> (checking IMX_I2C_I2DR and IBB) being in-place. Why is this
> work-around not sufficient?
By the time we discovered I2DR workaround we went far from version 2 of
driver and it wasn't tested. I'm sure that I2DR workaround will improve
stability, but I do not know if it will fix all issues (i.e. passing of
stress tests )
Best Regards,
Maxim Syrchin
> Thanks again,
>
> Joshua
>
>
> On 10/28/2016 04:38 AM, Maxim Syrchin wrote:
>> Hi,
>> Sorry for huge delay in answering. Unfortunately we don't have enough
>> resources now to prepare clean enough patch to be accepted by community.
>> Please find the latest version attached. Driver has passed stress
>> tests, but looks like it need seriuos refactoring (it is
>> unnecessarily complicated).
>> We still have polling in slave code. Since imx doesn't generate
>> interrupt on "bus busy"/"bus free" events we have to test IBB bit in
>> polling loop.
>> Comparing to v2 version several race conditions were fixed (bus
>> locking by slave, breaking slave transaction by starting master
>> xfer). v2 is working pretty good in slave-only or master-only mode.
>> It is reasonable to add slave locking test - sometimes imx6 hw
>> doesn't release data line. As workaround we use dummy reading of
>> IMX_I2C_I2DR if driver is in slave mode and IBB bit is set for a
>> long time.
>>
>> Thanks,
>> Maxim Syrchin
>>
>>
>> 27.10.2016 10:31, Frkuska, Joshua пишет:
>>> Hi Maxim, Dmitriy, Wolfram,
>>>
>>> If there is no immediate plan for a third release of the below patch
>>> set, would it be possible to continue with merging v2 after
>>> addressing the remaining concerns?
>>>
>>>
>>> Thank you and regards,
>>>
>>> Joshua
>>>> Hi Maxim,
>>>>
>>>> On 2016-03-04 11:06:10 in the thread "Re: [PATCH] i2c: imx: add
>>>> slave support. v2"
>>>> referenced here: https://patchwork.ozlabs.org/patch/573353/ you said:
>>>>> Hi Wolfram,
>>>>> I'm now working on creating new driver version. I think I'll be
>>>>> able to
>>>>> sent it soon.
>>>> Do you still plan to release a driver update for an i2c imx driver
>>>> slave support?
>>>>
>>>> Best regards,
>>>> Jim Baxter
>>>>
>>
>
^ permalink raw reply
* [PATCH] i2c: make busses/i2c-pxa-pci.c explicitly non-modular
From: Paul Gortmaker @ 2016-10-31 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Paul Gortmaker, Wolfram Sang, Sebastian Andrzej Siewior,
linux-i2c
The Kconfig currently controlling compilation of this code is:
drivers/i2c/busses/Kconfig:config I2C_PXA_PCI
drivers/i2c/busses/Kconfig: def_bool I2C_PXA && X86_32 && PCI && OF
...meaning that it currently is not being built as a module by anyone.
Lets remove the modular code that is essentially orphaned, so that
when reading the driver there is no doubt it is builtin-only.
We explicitly disallow a driver unbind, since that doesn't have a
sensible use case anyway, and it allows us to drop the ".remove"
code for non-modular drivers.
Since module_pci_driver() uses the same init level priority as
builtin_pci_driver() the init ordering remains unchanged with
this commit.
Also note that MODULE_DEVICE_TABLE is a no-op for non-modular code.
We also delete the MODULE_LICENSE tag etc. since all that information
was (or is now) contained at the top of the file in the comments.
Cc: Wolfram Sang <wsa@the-dreams.de>
Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Cc: linux-i2c@vger.kernel.org
Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com>
---
drivers/i2c/busses/i2c-pxa-pci.c | 32 +++++++++-----------------------
1 file changed, 9 insertions(+), 23 deletions(-)
diff --git a/drivers/i2c/busses/i2c-pxa-pci.c b/drivers/i2c/busses/i2c-pxa-pci.c
index 417464e9ea2a..004deb96afe3 100644
--- a/drivers/i2c/busses/i2c-pxa-pci.c
+++ b/drivers/i2c/busses/i2c-pxa-pci.c
@@ -1,9 +1,13 @@
/*
+ * CE4100 PCI-I2C glue code for PXA's driver
+ * Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ * License: GPL v2
+ *
* The CE4100's I2C device is more or less the same one as found on PXA.
* It does not support slave mode, the register slightly moved. This PCI
* device provides three bars, every contains a single I2C controller.
*/
-#include <linux/module.h>
+#include <linux/init.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/i2c/pxa-i2c.h>
@@ -134,35 +138,17 @@ err_mem:
return ret;
}
-static void ce4100_i2c_remove(struct pci_dev *dev)
-{
- struct ce4100_devices *sds;
- unsigned int i;
-
- sds = pci_get_drvdata(dev);
-
- for (i = 0; i < ARRAY_SIZE(sds->pdev); i++)
- platform_device_unregister(sds->pdev[i]);
-
- pci_disable_device(dev);
- kfree(sds);
-}
-
static const struct pci_device_id ce4100_i2c_devices[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x2e68)},
{ },
};
-MODULE_DEVICE_TABLE(pci, ce4100_i2c_devices);
static struct pci_driver ce4100_i2c_driver = {
+ .driver = {
+ .suppress_bind_attrs = true,
+ },
.name = "ce4100_i2c",
.id_table = ce4100_i2c_devices,
.probe = ce4100_i2c_probe,
- .remove = ce4100_i2c_remove,
};
-
-module_pci_driver(ce4100_i2c_driver);
-
-MODULE_DESCRIPTION("CE4100 PCI-I2C glue code for PXA's driver");
-MODULE_LICENSE("GPL v2");
-MODULE_AUTHOR("Sebastian Andrzej Siewior <bigeasy@linutronix.de>");
+builtin_pci_driver(ce4100_i2c_driver);
--
2.6.1
^ permalink raw reply related
* Re: [PATCHv6 08/11] i2c: match vendorless strings on the internal string length
From: Kieran Bingham @ 2016-10-31 15:01 UTC (permalink / raw)
To: Peter Rosin, Lee Jones
Cc: Wolfram Sang, linux-i2c, linux-kernel, Javier Martinez Canillas,
sameo
In-Reply-To: <bc87437c-c53e-b6f6-e9b1-2d488639317b@axentia.se>
Hi Peter,
Thanks for your review
On 31/10/16 13:55, Peter Rosin wrote:
> On 2016-10-26 10:53, Lee Jones wrote:
>> On Tue, 25 Oct 2016, Kieran Bingham wrote:
>>
>>> If a user provides a shortened string to match a device to the sysfs i2c
>>> interface it will match on the first string that contains that string
>>> prefix.
>>>
>>> for example:
>>> echo a 0x68 > /sys/bus/i2c/devices/i2c-2/new_device
>>>
>>> will match as3711, as3722, and ak8975 incorrectly.
>>>
>>> Signed-off-by: Kieran Bingham <kieran@bingham.xyz>
>>
>> Acked-by: Lee Jones <lee.jones@linaro.org>
>>
>>> ---
>>> drivers/i2c/i2c-core.c | 2 +-
>>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>>
>>> diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
>>> index 01bce56f733a..50c9cfdb87b7 100644
>>> --- a/drivers/i2c/i2c-core.c
>>> +++ b/drivers/i2c/i2c-core.c
>>> @@ -1708,7 +1708,7 @@ i2c_of_match_device_strip_vendor(const struct of_device_id *matches,
>>> else
>>> name++;
>>>
>>> - if (!strncasecmp(client->name, name, strlen(client->name)))
>>> + if (!strncasecmp(client->name, name, strlen(name)))
>>> return matches;
>>> }
>>>
>>
>
> Is that really so much better?
My original thought was that it verifies 'more' of the userspace input.
but...
> With this patch
> echo as3711CRAP 0x68 > /sys/...
> will match as3711.
>
> What if there is some as37112 driver that is the real target?
You're right - It looks like the only way to do this correctly is to
match the strncasecmp and the strlen of both strings.
So really we should be using sysfs_streq(). The only limitation there is
that this original code was performing a case-insensitive compare.
Lee - Where did the requirement for case insensitive matching come from
in your original code. Is it expected to be case-insensitive from the
I2C sysfs interface? or are dt-nodes expected to be case-sensitive?
Does anyone see reason that this shouldn't be using sysfs_streq()? or do
we need a sysfs_strcaseeq()...
> Cheers,
> Peter
>
--
Regards
Kieran Bingham
^ permalink raw reply
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