linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] Add IDT 89HPESx EEPROM/CSR driver
@ 2016-10-02 23:13 Serge Semin
  2016-10-30 13:53 ` Greg KH
  2016-11-28 22:38 ` [PATCH v2 0/2] eeprom: " Serge Semin
  0 siblings, 2 replies; 34+ messages in thread
From: Serge Semin @ 2016-10-02 23:13 UTC (permalink / raw)
  To: arnd, gregkh
  Cc: wsa, jdelvare, linux-i2c, linux-kernel, Sergey.Semin, Serge Semin

Hello linux folks,

    This driver primarily is developed to give an access to EEPROM of IDT
PCIe-switches. Such switches provide a simple SMBus interface to perform
IO-operations from/to EEPROM, which is located at private (also called master)
SMBus of the switches. Using that interface this driver creates a simple
binary sysfs-file in the device directory:
/sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
In case if read-only flag is specified in the device dts-node, user-space
applications won't be able to write to the EEPROM sysfs-node.

    Additionally IDT 89HPESx SMBus interface provides an ability to write/read
data of device CSRs. This driver exposes corresponding sysfs-file to perform
simple IO operations using that facility for just basic debug purpose.
Particularly next file is created in the device specific sysfs-directory:
/sys/bus/i2c/devices/<bus>-<devaddr>/csr
Format of the sysfs-node is:
$ cat /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
<CSR address>:<CSR value>
So reading the content of the sysfs-file gives current CSR address and
it value. If user-space application wishes to change current CSR address,
it can just write a proper value to the sysfs-file:
$ echo "<CSR address>" > /sys/bus/i2c/devices/<bus>-<devaddr>/csr
If it wants to change the CSR value as well, the format of the write
operation is:
$ echo "<CSR address>:<CSR value>" > \
        /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
CSR address and value can be any of hexadecimal, decimal or octal format.

    The driver supports the most of the commonly available SMBus operations:
SMBus i2c block, SMBus block, smbus word and byte. The code has been tested
to be built for x32/x64 MIPS architecture and tested on the x32 MIPS machine.
The patch was applied on top of commit c6935931c1894ff857616ff8549b61236a19148f
of master branch of repository
git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git


Thanks,

=============================
Serge V. Semin
Leading Programmer
Embedded SW development group
T-platforms
=============================

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>

---
 .../devicetree/bindings/misc/idt_89hpesx.txt       |   41 +
 drivers/misc/eeprom/Kconfig                        |   10 +
 drivers/misc/eeprom/Makefile                       |    1 +
 drivers/misc/eeprom/idt_89hpesx.c                  | 1483 ++++++++++++++++++++
 4 files changed, 1535 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
new file mode 100644
index 0000000..469cc93
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
@@ -0,0 +1,41 @@
+EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
+
+Required properties:
+  - compatible : should be "<manufacturer>,<type>"
+		 Basically there is only one manufacturer: idt, but some
+		 compatible devices may be produced in future. Following devices
+		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
+		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
+		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
+		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
+		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
+		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
+		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
+		 89hpes64h16ag2;
+		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
+		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
+		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
+		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
+		 89hpes48t12, 89hpes48t12g2.
+		 Current implementation of the driver doesn't have any device-
+		 specific functionalities. But since each of them differs
+		 by registers mapping, CSRs read/write restrictions can be
+		 added in future.
+  - reg :	 I2C address of the IDT 89HPES device.
+
+Optional properties:
+  - read-only :	 Parameterless property disables writes to the EEPROM
+  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
+		 (default value is 4096 bytes if option isn't specified)
+  - idt,eeaddr : Custom address of EEPROM device
+		 (If not specified IDT 89HPESx device will try to communicate
+		  with EEPROM sited by default address - 0x50)
+
+Example:
+	idt_pcie_sw@60 {
+		compatible = "idt,89hpes12nt3";
+		reg = <0x60>;
+		read-only;
+		idt,eesize = <65536>;
+		idt,eeaddr = <0x50>;
+	};
diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index c4e41c2..de58762 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -100,4 +100,14 @@ config EEPROM_DIGSY_MTC_CFG
 
 	  If unsure, say N.
 
+config EEPROM_IDT_89HPESX
+	tristate "IDT 89HPESx PCIe-swtiches EEPROM / CSR support"
+	depends on I2C && SYSFS
+	help
+	  Enable this driver to get read/write access to EEPROM / CSRs
+	  over IDT PCIe-swtich i2c-slave interface.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called idt_89hpesx.
+
 endmenu
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index fc1e81d..90a5262 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o
 obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
 obj-$(CONFIG_EEPROM_93XX46)	+= eeprom_93xx46.o
 obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
+obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c
new file mode 100644
index 0000000..f95f4f0
--- /dev/null
+++ b/drivers/misc/eeprom/idt_89hpesx.c
@@ -0,0 +1,1483 @@
+/*
+ *   This file is provided under a GPLv2 license.  When using or
+ *   redistributing this file, you may do so under that license.
+ *
+ *   GPL LICENSE SUMMARY
+ *
+ *   Copyright (C) 2016 T-Platforms All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or modify it
+ *   under the terms and conditions of the GNU General Public License,
+ *   version 2, as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful, but WITHOUT
+ *   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ *   more details.
+ *
+ *   You should have received a copy of the GNU General Public License along
+ *   with this program; if not, it can be found <http://www.gnu.org/licenses/>.
+ *
+ *   The full GNU General Public License is included in this distribution in
+ *   the file called "COPYING".
+ *
+ *   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.
+ *
+ * IDT PCIe-switch NTB Linux driver
+ *
+ * Contact Information:
+ * Serge Semin <fancer.lancer@gmail.com>, <Sergey.Semin@t-platforms.ru>
+ */
+/*
+ *           NOTE of the IDT 89HPESx SMBus-slave interface driver
+ *    This driver primarily is developed to have an access to EEPROM device of
+ * IDT PCIe-switches. IDT provides a simple SMBus interface to perform IO-
+ * operations from/to EEPROM, which is located at private (so called Master)
+ * SMBus of switches. Using that interface this the driver creates a simple
+ * binary sysfs-file in the device directory:
+ * /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
+ * In case if read-only flag is specified in the dts-node of device desription,
+ * User-space applications won't be able to write to the EEPROM sysfs-node.
+ *    Additionally IDT 89HPESx SMBus interface has an ability to write/read
+ * data of device CSRs. This driver exposes another sysfs-file to perform
+ * simple IO operations using that ability for just basic debug purpose.
+ * Particularly next file is created in the device specific sysfs-directory:
+ * /sys/bus/i2c/devices/<bus>-<devaddr>/csr
+ * Format of the sysfs-node is:
+ * $ cat /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
+ * <CSR address>:<CSR value>
+ * So reading the content of the sysfs-file gives current CSR address and
+ * it value. If User-space application wishes to change current CSR address,
+ * it can just write a proper value to the sysfs-file:
+ * $ echo "<CSR address>" > /sys/bus/i2c/devices/<bus>-<devaddr>/csr
+ * If it wants to change the CSR value as well, the format of the write
+ * operation is:
+ * $ echo "<CSR address>:<CSR value>" > \
+ *        /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
+ * CSR address and value can be any of hexadecimal, decimal or octal format.
+ */
+
+/*
+ * Note: You can load this module with either option 'dyndbg=+p' or define the
+ * next preprocessor constant
+ */
+/* #define DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/pci_ids.h>
+
+#define IDT_NAME		"89hpesx"
+#define IDT_89HPESX_DESC	"IDT 89HPESx SMBus-slave interface driver"
+#define IDT_89HPESX_VER		"1.0"
+
+MODULE_DESCRIPTION(IDT_89HPESX_DESC);
+MODULE_VERSION(IDT_89HPESX_VER);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("T-platforms");
+
+/*
+ * Some common constant used in the driver for better readability:
+ * @SUCCESS:	Success of a function execution
+ */
+#define SUCCESS (0)
+
+/*
+ * struct idt_89hpesx_dev - IDT 89HPESx device data structure
+ * @eesize:	Size of EEPROM in bytes (calculated from "idt,eecompatible")
+ * @eero:	EEPROM Read-only flag
+ * @eeaddr:	EEPROM custom address
+ *
+ * @inieecmd:	Initial cmd value for EEPROM read/write operations
+ * @inicsrcmd:	Initial cmd value for CSR read/write operations
+ * @iniccode:	Initialial command code value for IO-operations
+ *
+ * @csr:	CSR address to perform read operation
+ *
+ * @smb_write:	SMBus write method
+ * @smb_read:	SMBus read method
+ * @smb_mtx:	SMBus mutex
+ *
+ * @client:	i2c client used to perform IO operations
+ *
+ * @eenode:	EEPROM sysfs-node to read/write data to/from EEPROM
+ * @regnode:	Register sysfs-node to read/write CSRs
+ */
+struct idt_smb_seq;
+struct idt_89hpesx_dev {
+	u32 eesize;
+	bool eero;
+	u8 eeaddr;
+
+	u8 inieecmd;
+	u8 inicsrcmd;
+	u8 iniccode;
+
+	atomic_t csr;
+
+	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
+	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
+	struct mutex smb_mtx;
+
+	struct i2c_client *client;
+
+	struct bin_attribute eenode;
+	struct bin_attribute regnode;
+};
+#define to_pdev_kobj(__kobj) \
+	dev_get_drvdata(container_of(__kobj, struct device, kobj))
+
+/*
+ * struct idt_smb_seq - sequence of data to be read/written from/to IDT 89HPESx
+ * @ccode:	SMBus command code
+ * @bytecnt:	Byte count of operation
+ * @data:	Data to by written
+ */
+struct idt_smb_seq {
+	u8 ccode;
+	u8 bytecnt;
+	u8 *data;
+};
+
+/*
+ * struct idt_eeprom_seq - sequence of data to be read/written from/to EEPROM
+ * @cmd:	Transaction CMD
+ * @eeaddr:	EEPROM custom address
+ * @memaddr:	Internal memory address of EEPROM
+ * @data:	Data to be written at the memory address
+ */
+struct idt_eeprom_seq {
+	u8 cmd;
+	u8 eeaddr;
+	u16 memaddr;
+	u8 data;
+} __packed;
+
+/*
+ * struct idt_csr_seq - sequence of data to be read/written from/to CSR
+ * @cmd:	Transaction CMD
+ * @csraddr:	Internal IDT device CSR address
+ * @data:	Data to be read/written from/to the CSR address
+ */
+struct idt_csr_seq {
+	u8 cmd;
+	u16 csraddr;
+	u32 data;
+} __packed;
+
+/*
+ * SMBus command code macros
+ * @CCODE_END:		Indicates the end of transaction
+ * @CCODE_START:	Indicates the start of transaction
+ * @CCODE_CSR:		CSR read/write transaction
+ * @CCODE_EEPROM:	EEPROM read/write transaction
+ * @CCODE_BYTE:		Supplied data has BYTE length
+ * @CCODE_WORD:		Supplied data has WORD length
+ * @CCODE_BLOCK:	Supplied data has variable length passed in bytecnt
+ *			byte right following CCODE byte
+ */
+#define CCODE_END	((u8)0x01)
+#define CCODE_START	((u8)0x02)
+#define CCODE_CSR	((u8)0x00)
+#define CCODE_EEPROM	((u8)0x04)
+#define CCODE_BYTE	((u8)0x00)
+#define CCODE_WORD	((u8)0x20)
+#define CCODE_BLOCK	((u8)0x40)
+#define CCODE_PEC	((u8)0x80)
+
+/*
+ * EEPROM command macros
+ * @EEPROM_OP_WRITE:	EEPROM write operation
+ * @EEPROM_OP_READ:	EEPROM read operation
+ * @EEPROM_USA:		Use specified address of EEPROM
+ * @EEPROM_NAERR:	EEPROM device is not ready to respond
+ * @EEPROM_LAERR:	EEPROM arbitration loss error
+ * @EEPROM_MSS:		EEPROM misplace start & stop bits error
+ * @EEPROM_WR_CNT:	Bytes count to perform write operation
+ * @EEPROM_WRRD_CNT:	Bytes count to write before reading
+ * @EEPROM_RD_CNT:	Bytes count to perform read operation
+ * @EEPROM_DEF_SIZE:	Fall back size of EEPROM
+ * @EEPROM_DEF_ADDR:	Defatul EEPROM address
+ */
+#define EEPROM_OP_WRITE	((u8)0x00)
+#define EEPROM_OP_READ	((u8)0x01)
+#define EEPROM_USA	((u8)0x02)
+#define EEPROM_NAERR	((u8)0x08)
+#define EEPROM_LAERR    ((u8)0x10)
+#define EEPROM_MSS	((u8)0x20)
+#define EEPROM_WR_CNT	((u8)5)
+#define EEPROM_WRRD_CNT	((u8)4)
+#define EEPROM_RD_CNT	((u8)5)
+#define EEPROM_DEF_SIZE	((u16)4096)
+#define EEPROM_DEF_ADDR	((u8)0x50)
+
+/*
+ * CSR command macros
+ * @CSR_DWE:		Enable all four bytes of the operation
+ * @CSR_OP_WRITE:	CSR write operation
+ * @CSR_OP_READ:	CSR read operation
+ * @CSR_RERR:		Read operation error
+ * @CSR_WERR:		Write operation error
+ * @CSR_WR_CNT:		Bytes count to perform write operation
+ * @CSR_WRRD_CNT:	Bytes count to write before reading
+ * @CSR_RD_CNT:		Bytes count to perform read operation
+ * @CSR_MAX:		Maximum CSR address
+ * @CSR_DEF:		Default CSR address
+ * @CSR_REAL_ADDR:	CSR real unshifted address
+ */
+#define CSR_DWE			((u8)0x0F)
+#define CSR_OP_WRITE		((u8)0x00)
+#define CSR_OP_READ		((u8)0x10)
+#define CSR_RERR		((u8)0x40)
+#define CSR_WERR		((u8)0x80)
+#define CSR_WR_CNT		((u8)7)
+#define CSR_WRRD_CNT		((u8)3)
+#define CSR_RD_CNT		((u8)7)
+#define CSR_MAX			((u32)0x3FFFF)
+#define CSR_DEF			((u16)0x0000)
+#define CSR_REAL_ADDR(val)	((unsigned int)val << 2)
+
+/*
+ * IDT 89HPESx basic register
+ * @IDT_VIDDID_CSR:	PCIe VID and DID of IDT 89HPESx
+ * @IDT_VID_MASK:	Mask of VID
+ */
+#define IDT_VIDDID_CSR	((u32)0x0000)
+#define IDT_VID_MASK	((u32)0xFFFF)
+
+/*
+ * IDT 89HPESx can send NACK when new command is sent before previous one
+ * fininshed execution. In this case driver retries operation
+ * certain times.
+ * @RETRY_CNT:		Number of retries before giving up and fail
+ * @idt_smb_safe:	Generate a retry loop on corresponding SMBus method
+ */
+#define RETRY_CNT (128)
+#define idt_smb_safe(ops, args...) ({ \
+	int __retry = RETRY_CNT; \
+	s32 __sts; \
+	do { \
+		__sts = i2c_smbus_ ## ops ## _data(args); \
+	} while (__retry-- && __sts < SUCCESS); \
+	__sts; \
+})
+
+/*
+ * Wrapper dev_err/dev_warn/dev_info/dev_dbg macros
+ */
+#define dev_err_idt(pdev, args...) \
+	dev_err(&pdev->client->dev, ## args)
+#define dev_warn_idt(pdev, args...) \
+	dev_warn(&pdev->client->dev, ## args)
+#define dev_info_idt(pdev, args...) \
+	dev_info(&pdev->client->dev, ## args)
+#define dev_dbg_idt(pdev, args...) \
+	dev_dbg(&pdev->client->dev, ## args)
+
+/*===========================================================================
+ *                         i2c bus level IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_smb_write_byte() - SMBus write method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_byte(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied data sending byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Send data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != SUCCESS)
+			return (int)sts;
+	}
+
+	return SUCCESS;
+}
+
+/*
+ * idt_smb_read_byte() - SMBus read method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_byte(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied buffer receiving byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Read data from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < SUCCESS)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return SUCCESS;
+}
+
+/*
+ * idt_smb_write_word() - SMBus write method when I2C_SMBUS_BYTE_DATA and
+ *                        I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_word(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data sending two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Send word data to the device */
+		sts = idt_smb_safe(write_word, pdev->client, ccode,
+			*(u16 *)&seq->data[idx]);
+		if (sts != SUCCESS)
+			return (int)sts;
+	}
+
+	/* If there is odd number of bytes then send just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Send byte data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != SUCCESS)
+			return (int)sts;
+	}
+
+	return SUCCESS;
+}
+
+/*
+ * idt_smb_read_word() - SMBus read method when I2C_SMBUS_BYTE_DATA and
+ *                       I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_word(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data reading two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Read word data from the device */
+		sts = idt_smb_safe(read_word, pdev->client, ccode);
+		if (sts < SUCCESS)
+			return (int)sts;
+
+		*(u16 *)&seq->data[idx] = (u16)sts;
+	}
+
+	/* If there is odd number of bytes then receive just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Read last data byte from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < SUCCESS)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return SUCCESS;
+}
+
+/*
+ * idt_smb_write_block() - SMBus write method when I2C_SMBUS_BLOCK_DATA
+ *                         operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_block(struct idt_89hpesx_dev *pdev,
+			       const struct idt_smb_seq *seq)
+{
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send block of data to the device */
+	return idt_smb_safe(write_block, pdev->client, ccode, seq->bytecnt,
+		seq->data);
+}
+
+/*
+ * idt_smb_read_block() - SMBus read method when I2C_SMBUS_BLOCK_DATA
+ *                        operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_block(struct idt_89hpesx_dev *pdev,
+			      struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read block of data from the device */
+	sts = idt_smb_safe(read_block, pdev->client, ccode, seq->data);
+	if (sts != seq->bytecnt)
+		return (sts < SUCCESS ? sts : -ENODATA);
+
+	return SUCCESS;
+}
+
+/*
+ * idt_smb_write_i2c_block() - SMBus write method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                             operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ *
+ * NOTE It's usual SMBus write block operation, except the actual data length is
+ * sent as first byte of data
+ */
+static int idt_smb_write_i2c_block(struct idt_89hpesx_dev *pdev,
+				   const struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the data to send. Length byte must be added prior the data */
+	buf[0] = seq->bytecnt;
+	memcpy(&buf[1], seq->data, seq->bytecnt);
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send length and block of data to the device */
+	return idt_smb_safe(write_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+}
+
+/*
+ * idt_smb_read_i2c_block() - SMBus read method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                            operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ *
+ * NOTE It's usual SMBus read block operation, except the actual data length is
+ * retrieved as first byte of data
+ */
+static int idt_smb_read_i2c_block(struct idt_89hpesx_dev *pdev,
+				  struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+	s32 sts;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read length and block of data from the device */
+	sts = idt_smb_safe(read_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+	if (sts != seq->bytecnt + 1)
+		return (sts < SUCCESS ? sts : -ENODATA);
+	if (buf[0] != seq->bytecnt)
+		return -ENODATA;
+
+	/* Copy retrieved data to the output data buffer */
+	memcpy(seq->data, &buf[1], seq->bytecnt);
+
+	return SUCCESS;
+}
+
+/*===========================================================================
+ *                          EEPROM IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_eeprom_write() - EEPROM write operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to be written
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_write(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			    const u8 *data)
+{
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret, retry;
+	u16 idx;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/* Send data byte-by-byte, checking if it is successfully written */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Perform write operation */
+		smbseq.bytecnt = EEPROM_WR_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_WRITE;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		eeseq.data = data[idx];
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != SUCCESS) {
+			dev_err_idt(pdev,
+				"Failed to write 0x%04hx:0x%02hhx to eeprom",
+				memaddr, data[idx]);
+			goto err_mutex_unlock;
+		}
+
+		/*
+		 * Check whether the data is successfully written by reading
+		 * from the same EEPROM memory address. Sometimes EEPROM may
+		 * respond with NACK if it's busy with writing previous data,
+		 * so we need to perform a few attempts of read cycle
+		 */
+		retry = RETRY_CNT;
+		do {
+			/* Send EEPROM memory address to read data from */
+			smbseq.bytecnt = EEPROM_WRRD_CNT;
+			eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
+			ret = pdev->smb_write(pdev, &smbseq);
+			if (ret != SUCCESS) {
+				dev_err_idt(pdev,
+					"Failed to init mem address 0x%02hhx",
+					memaddr);
+				goto err_mutex_unlock;
+			}
+
+			/* Perform read operation */
+			smbseq.bytecnt = EEPROM_RD_CNT;
+			eeseq.data = ~data[idx];
+			ret = pdev->smb_read(pdev, &smbseq);
+			if (ret != SUCCESS) {
+				dev_err_idt(pdev,
+					"Failed to read mem address 0x%02hhx",
+					memaddr);
+				goto err_mutex_unlock;
+			}
+		} while (retry-- && (eeseq.cmd & EEPROM_NAERR));
+
+		/* Check whether IDT successfully sent data to EEPROM */
+		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
+			dev_err_idt(pdev, "Communication with EEPROM failed");
+			ret = -EREMOTEIO;
+			goto err_mutex_unlock;
+		}
+		if (eeseq.data != data[idx]) {
+			dev_err_idt(pdev,
+				"Values don't match 0x%02hhx != 0x%02hhx",
+				eeseq.data, data[idx]);
+			ret = -EREMOTEIO;
+			goto err_mutex_unlock;
+		}
+
+		/* Unlock IDT SMBus device */
+err_mutex_unlock:
+		mutex_unlock(&pdev->smb_mtx);
+		if (ret != SUCCESS)
+			return ret;
+	}
+
+	return SUCCESS;
+}
+
+/*
+ * idt_eeprom_read() - EEPROM read operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to read
+ * @buf:	Buffer to read data to
+ */
+static int idt_eeprom_read(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			   u8 *buf)
+{
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	u16 idx;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/* Send data one-by-one, checking if it is successfully written */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Send EEPROM memory address to read data from */
+		smbseq.bytecnt = EEPROM_WRRD_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != SUCCESS) {
+			dev_err_idt(pdev, "Failed to init mem address 0x%02hhx",
+				memaddr);
+			goto err_mutex_unlock;
+		}
+
+		/* Perform read operation (rest of fields stay the same) */
+		smbseq.bytecnt = EEPROM_RD_CNT;
+		ret = pdev->smb_read(pdev, &smbseq);
+		if (ret != SUCCESS) {
+			dev_err_idt(pdev,
+				"Failed to read eeprom address 0x%02hhx",
+				memaddr);
+			ret = -EREMOTEIO;
+			goto err_mutex_unlock;
+		}
+
+		/* Check whether IDT successfully read data from EEPROM */
+		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
+			dev_err_idt(pdev, "Communication with eeprom failed");
+			ret = -EREMOTEIO;
+			goto err_mutex_unlock;
+		}
+
+		/* Save retrieved data */
+		buf[idx] = eeseq.data;
+
+		/* Unlock IDT SMBus device */
+err_mutex_unlock:
+		mutex_unlock(&pdev->smb_mtx);
+		if (ret != SUCCESS)
+			return ret;
+	}
+
+	return SUCCESS;
+}
+
+/*===========================================================================
+ *                          CSR IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_csr_write() - CSR write operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_write(struct idt_89hpesx_dev *pdev, u16 csraddr,
+			 const u32 data)
+{
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Perform write operation */
+	smbseq.bytecnt = CSR_WR_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_WRITE;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	csrseq.data = cpu_to_le32(data);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != SUCCESS) {
+		dev_err_idt(pdev, "Failed to write 0x%04x: 0x%04x to csr",
+			CSR_REAL_ADDR(csraddr), data);
+		goto err_mutex_unlock;
+	}
+
+	/* Send CSR address to read data from */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != SUCCESS) {
+		dev_err_idt(pdev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != SUCCESS) {
+		dev_err_idt(pdev, "Failed to read csr 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err_idt(pdev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*
+ * idt_csr_read() - CSR read operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_read(struct idt_89hpesx_dev *pdev, u16 csraddr, u32 *data)
+{
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Send CSR register address before reading it */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != SUCCESS) {
+		dev_err_idt(pdev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != SUCCESS) {
+		dev_err_idt(pdev, "Failed to read csr 0x%04hx",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err_idt(pdev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Save data retrieved from IDT */
+	*data = le32_to_cpu(csrseq.data);
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*===========================================================================
+ *                          Sysfs-nodes IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_sysfs_eeprom_write() - EEPROM sysfs-node write callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t idt_sysfs_eeprom_write(struct file *filp, struct kobject *kobj,
+				      struct bin_attribute *attr,
+				      char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = to_pdev_kobj(kobj);
+
+	/* Perform EEPROM write operation */
+	ret = idt_eeprom_write(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != SUCCESS ? ret : count);
+}
+
+/*
+ * idt_sysfs_eeprom_read() - EEPROM sysfs-node read callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t idt_sysfs_eeprom_read(struct file *filp, struct kobject *kobj,
+				     struct bin_attribute *attr,
+				     char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = to_pdev_kobj(kobj);
+
+	/* Perform EEPROM read operation */
+	ret = idt_eeprom_read(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != SUCCESS ? ret : count);
+}
+
+/*
+ * idt_sysfs_csr_store() - CSR sysfs-node write callback
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @count:	Size of the buffer
+ *
+ * It accepts either "0x<reg addr>:0x<value>" for saving register address
+ * and writing value to specified DWORD register or "0x<reg addr>" for
+ * just saving register address in order to perform next read operation.
+ *
+ * WARNING No spaces are allowed. Incoming string must be strictly formated as:
+ * "<reg addr>:<value>". Register address must be aligned within 4 bytes
+ * (one DWORD).
+ */
+static ssize_t idt_sysfs_csr_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	char *colon_ch, *csraddr_str, *csrval_str;
+	int ret, csraddr_len, csrval_len;
+	u32 csraddr, csrval;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(dev);
+
+	/* Find position of colon in the buffer */
+	colon_ch = strnchr(buf, count, ':');
+
+	/*
+	 * If there is colon passed then new CSR value should be parsed as
+	 * well, so allocate buffer for CSR address substring.
+	 * If no colon is found, then string must have just one number with
+	 * no new CSR value
+	 */
+	if (colon_ch != NULL) {
+		csraddr_len = colon_ch - buf;
+		csraddr_str =
+			kmalloc(sizeof(char)*(csraddr_len + 1), GFP_KERNEL);
+		if (csraddr_str == NULL)
+			return -ENOMEM;
+		/* Copy the register address to the substring buffer */
+		strncpy(csraddr_str, buf, csraddr_len);
+		csraddr_str[csraddr_len] = '\0';
+		/* Register value must follow the colon */
+		csrval_str = colon_ch + 1;
+		csrval_len = strnlen(csrval_str, count - csraddr_len);
+	} else /* if (str_colon == NULL) */ {
+		csraddr_str = (char *)buf; /* Just to shut warning up */
+		csraddr_len = strnlen(csraddr_str, count);
+		csrval_str = NULL;
+		csrval_len = 0;
+	}
+
+	/* Convert CSR address to u32 value */
+	ret = kstrtou32(csraddr_str, 0, &csraddr);
+	if (ret != SUCCESS)
+		goto free_csraddr_str;
+
+	/* Check whether passed register address is valid */
+	if (csraddr > CSR_MAX || !IS_ALIGNED(csraddr, SZ_4)) {
+		ret = -EINVAL;
+		goto free_csraddr_str;
+	}
+
+	/* Shift register address to the right so to have u16 address */
+	csraddr >>= 2;
+
+	/* Parse new CSR value and send it to IDT, if colon has been found */
+	if (colon_ch != NULL) {
+		ret = kstrtou32(csrval_str, 0, &csrval);
+		if (ret != SUCCESS)
+			goto free_csraddr_str;
+
+		ret = idt_csr_write(pdev, (u16)csraddr, csrval);
+		if (ret != SUCCESS)
+			goto free_csraddr_str;
+	}
+
+	/* Save CSR address in the data structure for future read operations */
+	atomic_set(&pdev->csr, (int)csraddr);
+
+	/* Free memory only if colon has been found */
+free_csraddr_str:
+	if (colon_ch != NULL)
+		kfree(csraddr_str);
+
+	return (ret != SUCCESS ? ret : count);
+}
+
+/*
+ * idt_sysfs_csr_show() - CSR sysfs-node read callback
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ *
+ * It just prints the pair "0x<reg addr>:0x<value>" to passed buffer.
+ */
+static ssize_t idt_sysfs_csr_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct idt_89hpesx_dev *pdev;
+	u32 csraddr, csrval;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(dev);
+
+	/* Read current CSR address */
+	csraddr = atomic_read(&pdev->csr);
+
+	/* Perform CSR read operation */
+	ret = idt_csr_read(pdev, (u16)csraddr, &csrval);
+	if (ret != SUCCESS)
+		return ret;
+
+	/* Shift register address to the left so to have real address */
+	csraddr <<= 2;
+
+	/* Print the "0x<reg addr>:0x<value>" to buffer */
+	return snprintf(buf, PAGE_SIZE, "0x%05x:0x%08x\n",
+		(unsigned int)csraddr, (unsigned int)csrval);
+}
+
+/*
+ * eeprom_attribute - EEPROM sysfs-node attributes
+ *
+ * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
+ * be read-only as well if the corresponding flag is specified in OF node.
+ */
+static struct bin_attribute eeprom_attribute = {
+	.attr = {
+		.name = "eeprom",
+		.mode = S_IRUGO | S_IWUSR
+	},
+	.size = EEPROM_DEF_SIZE,
+	.write = idt_sysfs_eeprom_write,
+	.read = idt_sysfs_eeprom_read
+};
+
+/*
+ * csr_attribute - CSR sysfs-node attributes
+ */
+static struct device_attribute csr_attribute = {
+	.attr = {
+		.name = "csr",
+		.mode = S_IRUGO | S_IWUSR
+	},
+	.store = idt_sysfs_csr_store,
+	.show = idt_sysfs_csr_show,
+};
+
+/*===========================================================================
+ *                       Driver init/deinit methods
+ *===========================================================================
+ */
+
+/*
+ * idt_set_defval() - set default device data parameters
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_set_defval(struct idt_89hpesx_dev *pdev)
+{
+	/* If OF info is missing then use next values */
+	pdev->eesize = EEPROM_DEF_SIZE;
+	pdev->eero = true;
+	pdev->inieecmd = 0;
+	pdev->eeaddr = EEPROM_DEF_ADDR << 1;
+}
+
+#ifdef CONFIG_OF
+/*
+ * idt_get_ofdata() - get IDT i2c-device parameters from device tree
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	struct device_node *node = pdev->client->dev.of_node;
+	const __be32 *val;
+
+	/* Read dts node parameters */
+	if (node) {
+		/* Get EEPROM size from 'idt,eesize' */
+		val = of_get_property(node, "idt,eesize", NULL);
+		if (val != NULL) {
+			pdev->eesize = be32_to_cpup(val);
+			if (!is_power_of_2(pdev->eesize))
+				dev_warn_idt(pdev,
+					"EEPROM size %u is not power of 2",
+					pdev->eesize);
+		} else /* if (val == NULL) */ {
+			pdev->eesize = EEPROM_DEF_SIZE;
+			dev_warn_idt(pdev,
+				"No EEPROM size, set default %u bytes",
+				pdev->eesize);
+		}
+
+		/* Get custom EEPROM address from 'idt,eeaddr' */
+		val = of_get_property(node, "idt,eeaddr", NULL);
+		if (val != NULL) {
+			pdev->inieecmd = EEPROM_USA;
+			pdev->eeaddr = be32_to_cpup(val) << 1;
+		} else /* if (val != NULL) */ {
+			pdev->inieecmd = 0;
+			pdev->eeaddr = EEPROM_DEF_ADDR << 1;
+		}
+
+		/* Check EEPROM 'read-only' flag */
+		if (of_get_property(node, "read-only", NULL))
+			pdev->eero = true;
+		else /* if (!of_get_property(node, "read-only", NULL)) */
+			pdev->eero = false;
+	} else {
+		dev_warn_idt(pdev, "No dts node, set default values");
+		idt_set_defval(pdev);
+	}
+}
+#else
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	dev_warn_idt(pdev, "OF table is unsupported, set default values");
+
+	/* Nothing we can do, just set the default values */
+	idt_set_defval(pdev);
+}
+#endif /* CONFIG_OF */
+
+/*
+ * idt_create_pdev() - create and init data structure of the driver
+ * @client:	i2c client of IDT PCIe-switch device
+ */
+static struct idt_89hpesx_dev *idt_create_pdev(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev;
+
+	/* Allocate memory for driver data */
+	pdev = devm_kmalloc(&client->dev, sizeof(struct idt_89hpesx_dev),
+		GFP_KERNEL);
+	if (pdev == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	/* Initialize basic fields of the data */
+	pdev->client = client;
+	i2c_set_clientdata(client, pdev);
+
+	/* Read OF nodes information */
+	idt_get_ofdata(pdev);
+
+	/* Initialize basic CSR CMD field - use full DWORD-sized r/w ops */
+	pdev->inicsrcmd = CSR_DWE;
+	atomic_set(&pdev->csr, CSR_DEF);
+
+	/* Enable Packet Error Checking if it's supported by adapter */
+	if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) {
+		pdev->iniccode = CCODE_PEC;
+		client->flags |= I2C_CLIENT_PEC;
+	} else /* PEC is unsupported */ {
+		pdev->iniccode = 0;
+	}
+
+	dev_dbg_idt(pdev, "IDT 89HPESx data created");
+
+	return pdev;
+}
+
+/*
+ * idt_free_pdev() - free data structure of the driver
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_free_pdev(struct idt_89hpesx_dev *pdev)
+{
+	/* Clear driver data from device private field */
+	i2c_set_clientdata(pdev->client, NULL);
+
+	/* Just free memory allocated for data */
+	devm_kfree(&pdev->client->dev, pdev);
+
+	dev_dbg_idt(pdev, "IDT 89HPESx data discarded");
+}
+
+/*
+ * idt_set_smbus_ops() - set supported SMBus operations
+ * @pdev:	Pointer to the driver data
+ * Return status of smbus check operations
+ */
+static int idt_set_smbus_ops(struct idt_89hpesx_dev *pdev)
+{
+	struct i2c_adapter *adapter = pdev->client->adapter;
+
+	/* Check i2c adapter read functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_READ_BLOCK_DATA)) {
+		pdev->smb_read = idt_smb_read_block;
+		dev_dbg_idt(pdev, "SMBus block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+		pdev->smb_read = idt_smb_read_i2c_block;
+		dev_dbg_idt(pdev, "SMBus i2c-block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_word;
+		dev_warn_idt(pdev, "Use slow word/byte SMBus read ops");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_byte;
+		dev_warn_idt(pdev, "Use slow byte SMBus read op");
+	} else /* no supported smbus read operations */ {
+		dev_err_idt(pdev, "No supported SMBus read op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Check i2c adapter write functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) {
+		pdev->smb_write = idt_smb_write_block;
+		dev_dbg_idt(pdev, "SMBus block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
+		pdev->smb_write = idt_smb_write_i2c_block;
+		dev_dbg_idt(pdev, "SMBus i2c-block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_word;
+		dev_warn_idt(pdev, "Use slow word/byte SMBus write op");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_byte;
+		dev_warn_idt(pdev, "Use slow byte SMBus write op");
+	} else /* no supported smbus write operations */ {
+		dev_err_idt(pdev, "No supported SMBus write op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Initialize IDT SMBus slave interface mutex */
+	mutex_init(&pdev->smb_mtx);
+
+	dev_dbg_idt(pdev, "SMBus functionality successfully checked");
+
+	return SUCCESS;
+}
+
+/*
+ * idt_check_dev() - check whether it's really IDT 89HPESx device
+ * @pdev:	Pointer to the driver data
+ * Return status of i2c adapter check operation
+ */
+static int idt_check_dev(struct idt_89hpesx_dev *pdev)
+{
+	u32 viddid;
+	int ret;
+
+	/* Read VID and DID directly from IDT memory space */
+	ret = idt_csr_read(pdev, IDT_VIDDID_CSR, &viddid);
+	if (ret != SUCCESS) {
+		dev_err_idt(pdev, "Failed to read VID/DID");
+		return ret;
+	}
+
+	/* Check whether it's IDT device */
+	if ((viddid & IDT_VID_MASK) != PCI_VENDOR_ID_IDT) {
+		dev_err_idt(pdev, "Got unsupported VID/DID: 0x%08x", viddid);
+		return -ENODEV;
+	}
+
+	dev_info_idt(pdev, "Found IDT 89HPES device VID:0x%04x, DID:0x%04x",
+		(viddid & IDT_VID_MASK), (viddid >> 16));
+
+	return SUCCESS;
+}
+
+/*
+ * idt_create_sysfs_files() - create sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+static int idt_create_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	int ret;
+
+	/* In case of read-only EEPROM get rid of write ability */
+	if (pdev->eero) {
+		eeprom_attribute.attr.mode &= ~S_IWUSR;
+		eeprom_attribute.write = NULL;
+	}
+	/* Create EEPROM sysfs file */
+	eeprom_attribute.size = pdev->eesize;
+	ret = sysfs_create_bin_file(&dev->kobj, &eeprom_attribute);
+	if (ret != SUCCESS) {
+		dev_err_idt(pdev, "Failed to create EEPROM sysfs-node");
+		return ret;
+	}
+
+	/* Create CSR sysfs file */
+	ret = sysfs_create_file(&dev->kobj, &csr_attribute.attr);
+	if (ret != SUCCESS) {
+		dev_err_idt(pdev, "Failed to create CSR sysfs-node");
+		sysfs_remove_bin_file(&dev->kobj, &eeprom_attribute);
+		return ret;
+	}
+
+	dev_dbg_idt(pdev, "Sysfs-files created");
+
+	return SUCCESS;
+}
+
+/*
+ * idt_remove_sysfs_files() - remove sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	/* Remove CSR sysfs file */
+	sysfs_remove_file(&dev->kobj, &csr_attribute.attr);
+
+	/* Remove EEPROM sysfs file */
+	sysfs_remove_bin_file(&dev->kobj, &eeprom_attribute);
+
+	dev_dbg_idt(pdev, "Sysfs-files removed");
+}
+
+/*
+ * idt_probe() - IDT 89HPESx driver probe() callback method
+ */
+static int idt_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Create driver data */
+	pdev = idt_create_pdev(client);
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	/* Set SMBus operations */
+	ret = idt_set_smbus_ops(pdev);
+	if (ret != SUCCESS)
+		goto err_free_pdev;
+
+	/* Check whether it is truly IDT 89HPESx device */
+	ret = idt_check_dev(pdev);
+	if (ret != SUCCESS)
+		goto err_free_pdev;
+
+	/* Create sysfs files */
+	ret = idt_create_sysfs_files(pdev);
+	if (ret != SUCCESS)
+		goto err_free_pdev;
+
+	dev_dbg_idt(pdev, "IDT %s device probed", id->name);
+
+	return SUCCESS;
+
+err_free_pdev:
+	idt_free_pdev(pdev);
+
+	return ret;
+}
+
+/*
+ * idt_remove() - IDT 89HPESx driver remove() callback method
+ */
+static int idt_remove(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev = i2c_get_clientdata(client);
+
+	/* Remove sysfs files first */
+	idt_remove_sysfs_files(pdev);
+
+	/* Discard driver data structure */
+	idt_free_pdev(pdev);
+
+	dev_dbg_idt(pdev, "IDT 89HPESx device removed");
+
+	return SUCCESS;
+}
+
+/*
+ * idt_ids - supported IDT 89HPESx devices
+ */
+static const struct i2c_device_id idt_ids[] = {
+	{ "89hpes8nt2", 0 },
+	{ "89hpes12nt3", 0 },
+
+	{ "89hpes24nt6ag2", 0 },
+	{ "89hpes32nt8ag2", 0 },
+	{ "89hpes32nt8bg2", 0 },
+	{ "89hpes12nt12g2", 0 },
+	{ "89hpes16nt16g2", 0 },
+	{ "89hpes24nt24g2", 0 },
+	{ "89hpes32nt24ag2", 0 },
+	{ "89hpes32nt24bg2", 0 },
+
+	{ "89hpes12n3", 0 },
+	{ "89hpes12n3a", 0 },
+	{ "89hpes24n3", 0 },
+	{ "89hpes24n3a", 0 },
+
+	{ "89hpes32h8", 0 },
+	{ "89hpes32h8g2", 0 },
+	{ "89hpes48h12", 0 },
+	{ "89hpes48h12g2", 0 },
+	{ "89hpes48h12ag2", 0 },
+	{ "89hpes16h16", 0 },
+	{ "89hpes22h16", 0 },
+	{ "89hpes22h16g2", 0 },
+	{ "89hpes34h16", 0 },
+	{ "89hpes34h16g2", 0 },
+	{ "89hpes64h16", 0 },
+	{ "89hpes64h16g2", 0 },
+	{ "89hpes64h16ag2", 0 },
+
+	/* { "89hpes3t3", 0 }, // No SMBus-slave iface */
+	{ "89hpes12t3g2", 0 },
+	{ "89hpes24t3g2", 0 },
+	/* { "89hpes4t4", 0 }, // No SMBus-slave iface */
+	{ "89hpes16t4", 0 },
+	{ "89hpes4t4g2", 0 },
+	{ "89hpes10t4g2", 0 },
+	{ "89hpes16t4g2", 0 },
+	{ "89hpes16t4ag2", 0 },
+	{ "89hpes5t5", 0 },
+	{ "89hpes6t5", 0 },
+	{ "89hpes8t5", 0 },
+	{ "89hpes8t5a", 0 },
+	{ "89hpes24t6", 0 },
+	{ "89hpes6t6g2", 0 },
+	{ "89hpes24t6g2", 0 },
+	{ "89hpes16t7", 0 },
+	{ "89hpes32t8", 0 },
+	{ "89hpes32t8g2", 0 },
+	{ "89hpes48t12", 0 },
+	{ "89hpes48t12g2", 0 },
+	{ /* END OF LIST */ }
+};
+MODULE_DEVICE_TABLE(i2c, idt_ids);
+
+/*
+ * idt_driver - IDT 89HPESx driver structure
+ */
+static struct i2c_driver idt_driver = {
+	.driver = {
+		.name = IDT_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = idt_probe,
+	.remove = idt_remove,
+	.id_table = idt_ids,
+};
+module_i2c_driver(idt_driver);
+
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH] Add IDT 89HPESx EEPROM/CSR driver
  2016-10-02 23:13 [PATCH] Add IDT 89HPESx EEPROM/CSR driver Serge Semin
@ 2016-10-30 13:53 ` Greg KH
  2016-11-24 18:42   ` Serge Semin
  2016-11-28 22:38 ` [PATCH v2 0/2] eeprom: " Serge Semin
  1 sibling, 1 reply; 34+ messages in thread
From: Greg KH @ 2016-10-30 13:53 UTC (permalink / raw)
  To: Serge Semin; +Cc: arnd, wsa, jdelvare, linux-i2c, linux-kernel, Sergey.Semin

On Mon, Oct 03, 2016 at 02:13:45AM +0300, Serge Semin wrote:
> Hello linux folks,
> 
>     This driver primarily is developed to give an access to EEPROM of IDT
> PCIe-switches. Such switches provide a simple SMBus interface to perform
> IO-operations from/to EEPROM, which is located at private (also called master)
> SMBus of the switches. Using that interface this driver creates a simple
> binary sysfs-file in the device directory:
> /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
> In case if read-only flag is specified in the device dts-node, user-space
> applications won't be able to write to the EEPROM sysfs-node.
> 
>     Additionally IDT 89HPESx SMBus interface provides an ability to write/read
> data of device CSRs. This driver exposes corresponding sysfs-file to perform
> simple IO operations using that facility for just basic debug purpose.

If it's for debugging, please put it in debugfs, not in sysfs.  sysfs
files for one-off drivers is usually discouraged, but at the least, you
have to document it in a Documentation/ABI/ file.  For debugfs files, we
don't care, you can do whatever you want in them :)

> Particularly next file is created in the device specific sysfs-directory:
> /sys/bus/i2c/devices/<bus>-<devaddr>/csr
> Format of the sysfs-node is:
> $ cat /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
> <CSR address>:<CSR value>
> So reading the content of the sysfs-file gives current CSR address and
> it value. If user-space application wishes to change current CSR address,
> it can just write a proper value to the sysfs-file:
> $ echo "<CSR address>" > /sys/bus/i2c/devices/<bus>-<devaddr>/csr
> If it wants to change the CSR value as well, the format of the write
> operation is:
> $ echo "<CSR address>:<CSR value>" > \
>         /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
> CSR address and value can be any of hexadecimal, decimal or octal format.

Yeah, that all should go into debugfs.

>     The driver supports the most of the commonly available SMBus operations:
> SMBus i2c block, SMBus block, smbus word and byte. The code has been tested
> to be built for x32/x64 MIPS architecture and tested on the x32 MIPS machine.
> The patch was applied on top of commit c6935931c1894ff857616ff8549b61236a19148f
> of master branch of repository
> git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git
> 
> 
> Thanks,
> 
> =============================
> Serge V. Semin
> Leading Programmer
> Embedded SW development group
> T-platforms
> =============================
> 
> Signed-off-by: Serge Semin <fancer.lancer@gmail.com>

meta-comment, the "hello", and "thanks" and signature doesn't need to be
in a changelog text, next time you can drop that.  See the many kernel
patches on the mailing lists for examples of how to do this.


> 
> ---
>  .../devicetree/bindings/misc/idt_89hpesx.txt       |   41 +


Can you split this out into a separate file and be sure to cc: the patch
to the devicetree maintainers?

>  drivers/misc/eeprom/Kconfig                        |   10 +
>  drivers/misc/eeprom/Makefile                       |    1 +
>  drivers/misc/eeprom/idt_89hpesx.c                  | 1483 ++++++++++++++++++++
>  4 files changed, 1535 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
>  create mode 100644 drivers/misc/eeprom/idt_89hpesx.c
> 
> diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> new file mode 100644
> index 0000000..469cc93
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> @@ -0,0 +1,41 @@
> +EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
> +
> +Required properties:
> +  - compatible : should be "<manufacturer>,<type>"
> +		 Basically there is only one manufacturer: idt, but some
> +		 compatible devices may be produced in future. Following devices
> +		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
> +		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
> +		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
> +		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
> +		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
> +		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
> +		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
> +		 89hpes64h16ag2;
> +		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
> +		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
> +		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
> +		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
> +		 89hpes48t12, 89hpes48t12g2.
> +		 Current implementation of the driver doesn't have any device-
> +		 specific functionalities. But since each of them differs
> +		 by registers mapping, CSRs read/write restrictions can be
> +		 added in future.
> +  - reg :	 I2C address of the IDT 89HPES device.
> +
> +Optional properties:
> +  - read-only :	 Parameterless property disables writes to the EEPROM
> +  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
> +		 (default value is 4096 bytes if option isn't specified)
> +  - idt,eeaddr : Custom address of EEPROM device
> +		 (If not specified IDT 89HPESx device will try to communicate
> +		  with EEPROM sited by default address - 0x50)
> +
> +Example:
> +	idt_pcie_sw@60 {
> +		compatible = "idt,89hpes12nt3";
> +		reg = <0x60>;
> +		read-only;
> +		idt,eesize = <65536>;
> +		idt,eeaddr = <0x50>;
> +	};
> diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
> index c4e41c2..de58762 100644
> --- a/drivers/misc/eeprom/Kconfig
> +++ b/drivers/misc/eeprom/Kconfig
> @@ -100,4 +100,14 @@ config EEPROM_DIGSY_MTC_CFG
>  
>  	  If unsure, say N.
>  
> +config EEPROM_IDT_89HPESX
> +	tristate "IDT 89HPESx PCIe-swtiches EEPROM / CSR support"
> +	depends on I2C && SYSFS
> +	help
> +	  Enable this driver to get read/write access to EEPROM / CSRs
> +	  over IDT PCIe-swtich i2c-slave interface.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called idt_89hpesx.
> +
>  endmenu
> diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
> index fc1e81d..90a5262 100644
> --- a/drivers/misc/eeprom/Makefile
> +++ b/drivers/misc/eeprom/Makefile
> @@ -5,3 +5,4 @@ obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o
>  obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
>  obj-$(CONFIG_EEPROM_93XX46)	+= eeprom_93xx46.o
>  obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
> +obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
> diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c
> new file mode 100644
> index 0000000..f95f4f0
> --- /dev/null
> +++ b/drivers/misc/eeprom/idt_89hpesx.c
> @@ -0,0 +1,1483 @@
> +/*
> + *   This file is provided under a GPLv2 license.  When using or
> + *   redistributing this file, you may do so under that license.
> + *
> + *   GPL LICENSE SUMMARY
> + *
> + *   Copyright (C) 2016 T-Platforms All Rights Reserved.
> + *
> + *   This program is free software; you can redistribute it and/or modify it
> + *   under the terms and conditions of the GNU General Public License,
> + *   version 2, as published by the Free Software Foundation.
> + *
> + *   This program is distributed in the hope that it will be useful, but WITHOUT
> + *   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + *   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
> + *   more details.
> + *
> + *   You should have received a copy of the GNU General Public License along
> + *   with this program; if not, it can be found <http://www.gnu.org/licenses/>.
> + *
> + *   The full GNU General Public License is included in this distribution in
> + *   the file called "COPYING".
> + *
> + *   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.
> + *
> + * IDT PCIe-switch NTB Linux driver
> + *
> + * Contact Information:
> + * Serge Semin <fancer.lancer@gmail.com>, <Sergey.Semin@t-platforms.ru>
> + */
> +/*
> + *           NOTE of the IDT 89HPESx SMBus-slave interface driver
> + *    This driver primarily is developed to have an access to EEPROM device of
> + * IDT PCIe-switches. IDT provides a simple SMBus interface to perform IO-
> + * operations from/to EEPROM, which is located at private (so called Master)
> + * SMBus of switches. Using that interface this the driver creates a simple
> + * binary sysfs-file in the device directory:
> + * /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
> + * In case if read-only flag is specified in the dts-node of device desription,
> + * User-space applications won't be able to write to the EEPROM sysfs-node.
> + *    Additionally IDT 89HPESx SMBus interface has an ability to write/read
> + * data of device CSRs. This driver exposes another sysfs-file to perform
> + * simple IO operations using that ability for just basic debug purpose.
> + * Particularly next file is created in the device specific sysfs-directory:
> + * /sys/bus/i2c/devices/<bus>-<devaddr>/csr
> + * Format of the sysfs-node is:
> + * $ cat /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
> + * <CSR address>:<CSR value>
> + * So reading the content of the sysfs-file gives current CSR address and
> + * it value. If User-space application wishes to change current CSR address,
> + * it can just write a proper value to the sysfs-file:
> + * $ echo "<CSR address>" > /sys/bus/i2c/devices/<bus>-<devaddr>/csr
> + * If it wants to change the CSR value as well, the format of the write
> + * operation is:
> + * $ echo "<CSR address>:<CSR value>" > \
> + *        /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
> + * CSR address and value can be any of hexadecimal, decimal or octal format.
> + */
> +
> +/*
> + * Note: You can load this module with either option 'dyndbg=+p' or define the
> + * next preprocessor constant

What does this option do?  I don't understand it.

> + */
> +/* #define DEBUG */

Ah, just drop all of this, dynamic debugging is known how to be used
through the whole kernel, no need to document it again :)

> +
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include <linux/sizes.h>
> +#include <linux/slab.h>
> +#include <linux/mutex.h>
> +#include <linux/sysfs.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/of.h>
> +#include <linux/i2c.h>
> +#include <linux/pci_ids.h>

why pci_ids.h?

> +
> +#define IDT_NAME		"89hpesx"
> +#define IDT_89HPESX_DESC	"IDT 89HPESx SMBus-slave interface driver"
> +#define IDT_89HPESX_VER		"1.0"
> +
> +MODULE_DESCRIPTION(IDT_89HPESX_DESC);
> +MODULE_VERSION(IDT_89HPESX_VER);
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("T-platforms");
> +
> +/*
> + * Some common constant used in the driver for better readability:
> + * @SUCCESS:	Success of a function execution
> + */
> +#define SUCCESS (0)

No need to define this, we all know that 0 is "success", this is the
kernel.

> +
> +/*
> + * struct idt_89hpesx_dev - IDT 89HPESx device data structure
> + * @eesize:	Size of EEPROM in bytes (calculated from "idt,eecompatible")
> + * @eero:	EEPROM Read-only flag
> + * @eeaddr:	EEPROM custom address
> + *
> + * @inieecmd:	Initial cmd value for EEPROM read/write operations
> + * @inicsrcmd:	Initial cmd value for CSR read/write operations
> + * @iniccode:	Initialial command code value for IO-operations
> + *
> + * @csr:	CSR address to perform read operation
> + *
> + * @smb_write:	SMBus write method
> + * @smb_read:	SMBus read method
> + * @smb_mtx:	SMBus mutex
> + *
> + * @client:	i2c client used to perform IO operations
> + *
> + * @eenode:	EEPROM sysfs-node to read/write data to/from EEPROM
> + * @regnode:	Register sysfs-node to read/write CSRs
> + */
> +struct idt_smb_seq;
> +struct idt_89hpesx_dev {
> +	u32 eesize;
> +	bool eero;
> +	u8 eeaddr;
> +
> +	u8 inieecmd;
> +	u8 inicsrcmd;
> +	u8 iniccode;
> +
> +	atomic_t csr;
> +
> +	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
> +	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
> +	struct mutex smb_mtx;
> +
> +	struct i2c_client *client;
> +
> +	struct bin_attribute eenode;
> +	struct bin_attribute regnode;
> +};
> +#define to_pdev_kobj(__kobj) \
> +	dev_get_drvdata(container_of(__kobj, struct device, kobj))
> +
> +/*
> + * struct idt_smb_seq - sequence of data to be read/written from/to IDT 89HPESx
> + * @ccode:	SMBus command code
> + * @bytecnt:	Byte count of operation
> + * @data:	Data to by written
> + */
> +struct idt_smb_seq {
> +	u8 ccode;
> +	u8 bytecnt;
> +	u8 *data;
> +};
> +
> +/*
> + * struct idt_eeprom_seq - sequence of data to be read/written from/to EEPROM
> + * @cmd:	Transaction CMD
> + * @eeaddr:	EEPROM custom address
> + * @memaddr:	Internal memory address of EEPROM
> + * @data:	Data to be written at the memory address
> + */
> +struct idt_eeprom_seq {
> +	u8 cmd;
> +	u8 eeaddr;
> +	u16 memaddr;

endian?  Is this big or little?

> +	u8 data;
> +} __packed;
> +
> +/*
> + * struct idt_csr_seq - sequence of data to be read/written from/to CSR
> + * @cmd:	Transaction CMD
> + * @csraddr:	Internal IDT device CSR address
> + * @data:	Data to be read/written from/to the CSR address
> + */
> +struct idt_csr_seq {
> +	u8 cmd;
> +	u16 csraddr;
> +	u32 data;

Same for these, what endian are they?

> +} __packed;
> +
> +/*
> + * SMBus command code macros
> + * @CCODE_END:		Indicates the end of transaction
> + * @CCODE_START:	Indicates the start of transaction
> + * @CCODE_CSR:		CSR read/write transaction
> + * @CCODE_EEPROM:	EEPROM read/write transaction
> + * @CCODE_BYTE:		Supplied data has BYTE length
> + * @CCODE_WORD:		Supplied data has WORD length
> + * @CCODE_BLOCK:	Supplied data has variable length passed in bytecnt
> + *			byte right following CCODE byte
> + */
> +#define CCODE_END	((u8)0x01)
> +#define CCODE_START	((u8)0x02)
> +#define CCODE_CSR	((u8)0x00)
> +#define CCODE_EEPROM	((u8)0x04)
> +#define CCODE_BYTE	((u8)0x00)
> +#define CCODE_WORD	((u8)0x20)
> +#define CCODE_BLOCK	((u8)0x40)
> +#define CCODE_PEC	((u8)0x80)
> +
> +/*
> + * EEPROM command macros
> + * @EEPROM_OP_WRITE:	EEPROM write operation
> + * @EEPROM_OP_READ:	EEPROM read operation
> + * @EEPROM_USA:		Use specified address of EEPROM
> + * @EEPROM_NAERR:	EEPROM device is not ready to respond
> + * @EEPROM_LAERR:	EEPROM arbitration loss error
> + * @EEPROM_MSS:		EEPROM misplace start & stop bits error
> + * @EEPROM_WR_CNT:	Bytes count to perform write operation
> + * @EEPROM_WRRD_CNT:	Bytes count to write before reading
> + * @EEPROM_RD_CNT:	Bytes count to perform read operation
> + * @EEPROM_DEF_SIZE:	Fall back size of EEPROM
> + * @EEPROM_DEF_ADDR:	Defatul EEPROM address
> + */
> +#define EEPROM_OP_WRITE	((u8)0x00)
> +#define EEPROM_OP_READ	((u8)0x01)
> +#define EEPROM_USA	((u8)0x02)
> +#define EEPROM_NAERR	((u8)0x08)
> +#define EEPROM_LAERR    ((u8)0x10)
> +#define EEPROM_MSS	((u8)0x20)
> +#define EEPROM_WR_CNT	((u8)5)
> +#define EEPROM_WRRD_CNT	((u8)4)
> +#define EEPROM_RD_CNT	((u8)5)
> +#define EEPROM_DEF_SIZE	((u16)4096)
> +#define EEPROM_DEF_ADDR	((u8)0x50)
> +
> +/*
> + * CSR command macros
> + * @CSR_DWE:		Enable all four bytes of the operation
> + * @CSR_OP_WRITE:	CSR write operation
> + * @CSR_OP_READ:	CSR read operation
> + * @CSR_RERR:		Read operation error
> + * @CSR_WERR:		Write operation error
> + * @CSR_WR_CNT:		Bytes count to perform write operation
> + * @CSR_WRRD_CNT:	Bytes count to write before reading
> + * @CSR_RD_CNT:		Bytes count to perform read operation
> + * @CSR_MAX:		Maximum CSR address
> + * @CSR_DEF:		Default CSR address
> + * @CSR_REAL_ADDR:	CSR real unshifted address
> + */
> +#define CSR_DWE			((u8)0x0F)
> +#define CSR_OP_WRITE		((u8)0x00)
> +#define CSR_OP_READ		((u8)0x10)
> +#define CSR_RERR		((u8)0x40)
> +#define CSR_WERR		((u8)0x80)
> +#define CSR_WR_CNT		((u8)7)
> +#define CSR_WRRD_CNT		((u8)3)
> +#define CSR_RD_CNT		((u8)7)
> +#define CSR_MAX			((u32)0x3FFFF)
> +#define CSR_DEF			((u16)0x0000)
> +#define CSR_REAL_ADDR(val)	((unsigned int)val << 2)
> +
> +/*
> + * IDT 89HPESx basic register
> + * @IDT_VIDDID_CSR:	PCIe VID and DID of IDT 89HPESx
> + * @IDT_VID_MASK:	Mask of VID
> + */
> +#define IDT_VIDDID_CSR	((u32)0x0000)
> +#define IDT_VID_MASK	((u32)0xFFFF)
> +
> +/*
> + * IDT 89HPESx can send NACK when new command is sent before previous one
> + * fininshed execution. In this case driver retries operation
> + * certain times.
> + * @RETRY_CNT:		Number of retries before giving up and fail
> + * @idt_smb_safe:	Generate a retry loop on corresponding SMBus method
> + */
> +#define RETRY_CNT (128)
> +#define idt_smb_safe(ops, args...) ({ \
> +	int __retry = RETRY_CNT; \
> +	s32 __sts; \
> +	do { \
> +		__sts = i2c_smbus_ ## ops ## _data(args); \
> +	} while (__retry-- && __sts < SUCCESS); \
> +	__sts; \
> +})
> +
> +/*
> + * Wrapper dev_err/dev_warn/dev_info/dev_dbg macros
> + */
> +#define dev_err_idt(pdev, args...) \
> +	dev_err(&pdev->client->dev, ## args)
> +#define dev_warn_idt(pdev, args...) \
> +	dev_warn(&pdev->client->dev, ## args)
> +#define dev_info_idt(pdev, args...) \
> +	dev_info(&pdev->client->dev, ## args)
> +#define dev_dbg_idt(pdev, args...) \
> +	dev_dbg(&pdev->client->dev, ## args)

ick, don't wrap them, just use them as-is, it's not that hard to write
them out please.

> +
> +/*===========================================================================
> + *                         i2c bus level IO-operations
> + *===========================================================================
> + */
> +
> +/*
> + * idt_smb_write_byte() - SMBus write method when I2C_SMBUS_BYTE_DATA operation
> + *                        is only available
> + * @pdev:	Pointer to the driver data
> + * @seq:	Sequence of data to be written
> + */
> +static int idt_smb_write_byte(struct idt_89hpesx_dev *pdev,
> +			      const struct idt_smb_seq *seq)
> +{
> +	s32 sts;
> +	u8 ccode;
> +	int idx;
> +
> +	/* Loop over the supplied data sending byte one-by-one */
> +	for (idx = 0; idx < seq->bytecnt; idx++) {
> +		/* Collect the command code byte */
> +		ccode = seq->ccode | CCODE_BYTE;
> +		if (idx == 0)
> +			ccode |= CCODE_START;
> +		if (idx == seq->bytecnt - 1)
> +			ccode |= CCODE_END;
> +
> +		/* Send data to the device */
> +		sts = idt_smb_safe(write_byte, pdev->client, ccode,
> +			seq->data[idx]);
> +		if (sts != SUCCESS)
> +			return (int)sts;
> +	}
> +
> +	return SUCCESS;
> +}
> +
> +/*
> + * idt_smb_read_byte() - SMBus read method when I2C_SMBUS_BYTE_DATA operation
> + *                        is only available
> + * @pdev:	Pointer to the driver data
> + * @seq:	Buffer to read data to
> + */
> +static int idt_smb_read_byte(struct idt_89hpesx_dev *pdev,
> +			     struct idt_smb_seq *seq)
> +{
> +	s32 sts;
> +	u8 ccode;
> +	int idx;
> +
> +	/* Loop over the supplied buffer receiving byte one-by-one */
> +	for (idx = 0; idx < seq->bytecnt; idx++) {
> +		/* Collect the command code byte */
> +		ccode = seq->ccode | CCODE_BYTE;
> +		if (idx == 0)
> +			ccode |= CCODE_START;
> +		if (idx == seq->bytecnt - 1)
> +			ccode |= CCODE_END;
> +
> +		/* Read data from the device */
> +		sts = idt_smb_safe(read_byte, pdev->client, ccode);
> +		if (sts < SUCCESS)
> +			return (int)sts;
> +
> +		seq->data[idx] = (u8)sts;
> +	}
> +
> +	return SUCCESS;
> +}
> +
> +/*
> + * idt_smb_write_word() - SMBus write method when I2C_SMBUS_BYTE_DATA and
> + *                        I2C_FUNC_SMBUS_WORD_DATA operations are available
> + * @pdev:	Pointer to the driver data
> + * @seq:	Sequence of data to be written
> + */
> +static int idt_smb_write_word(struct idt_89hpesx_dev *pdev,
> +			      const struct idt_smb_seq *seq)
> +{
> +	s32 sts;
> +	u8 ccode;
> +	int idx, evencnt;
> +
> +	/* Calculate the even count of data to send */
> +	evencnt = seq->bytecnt - (seq->bytecnt % 2);
> +
> +	/* Loop over the supplied data sending two bytes at a time */
> +	for (idx = 0; idx < evencnt; idx += 2) {
> +		/* Collect the command code byte */
> +		ccode = seq->ccode | CCODE_WORD;
> +		if (idx == 0)
> +			ccode |= CCODE_START;
> +		if (idx == evencnt - 2)
> +			ccode |= CCODE_END;
> +
> +		/* Send word data to the device */
> +		sts = idt_smb_safe(write_word, pdev->client, ccode,
> +			*(u16 *)&seq->data[idx]);
> +		if (sts != SUCCESS)
> +			return (int)sts;
> +	}
> +
> +	/* If there is odd number of bytes then send just one last byte */
> +	if (seq->bytecnt != evencnt) {
> +		/* Collect the command code byte */
> +		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
> +		if (idx == 0)
> +			ccode |= CCODE_START;
> +
> +		/* Send byte data to the device */
> +		sts = idt_smb_safe(write_byte, pdev->client, ccode,
> +			seq->data[idx]);
> +		if (sts != SUCCESS)
> +			return (int)sts;
> +	}
> +
> +	return SUCCESS;
> +}
> +
> +/*
> + * idt_smb_read_word() - SMBus read method when I2C_SMBUS_BYTE_DATA and
> + *                       I2C_FUNC_SMBUS_WORD_DATA operations are available
> + * @pdev:	Pointer to the driver data
> + * @seq:	Buffer to read data to
> + */
> +static int idt_smb_read_word(struct idt_89hpesx_dev *pdev,
> +			     struct idt_smb_seq *seq)
> +{
> +	s32 sts;
> +	u8 ccode;
> +	int idx, evencnt;
> +
> +	/* Calculate the even count of data to send */
> +	evencnt = seq->bytecnt - (seq->bytecnt % 2);
> +
> +	/* Loop over the supplied data reading two bytes at a time */
> +	for (idx = 0; idx < evencnt; idx += 2) {
> +		/* Collect the command code byte */
> +		ccode = seq->ccode | CCODE_WORD;
> +		if (idx == 0)
> +			ccode |= CCODE_START;
> +		if (idx == evencnt - 2)
> +			ccode |= CCODE_END;
> +
> +		/* Read word data from the device */
> +		sts = idt_smb_safe(read_word, pdev->client, ccode);
> +		if (sts < SUCCESS)
> +			return (int)sts;
> +
> +		*(u16 *)&seq->data[idx] = (u16)sts;
> +	}
> +
> +	/* If there is odd number of bytes then receive just one last byte */
> +	if (seq->bytecnt != evencnt) {
> +		/* Collect the command code byte */
> +		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
> +		if (idx == 0)
> +			ccode |= CCODE_START;
> +
> +		/* Read last data byte from the device */
> +		sts = idt_smb_safe(read_byte, pdev->client, ccode);
> +		if (sts < SUCCESS)
> +			return (int)sts;
> +
> +		seq->data[idx] = (u8)sts;
> +	}
> +
> +	return SUCCESS;
> +}
> +
> +/*
> + * idt_smb_write_block() - SMBus write method when I2C_SMBUS_BLOCK_DATA
> + *                         operation is available
> + * @pdev:	Pointer to the driver data
> + * @seq:	Sequence of data to be written
> + */
> +static int idt_smb_write_block(struct idt_89hpesx_dev *pdev,
> +			       const struct idt_smb_seq *seq)
> +{
> +	u8 ccode;
> +
> +	/* Return error if too much data passed to send */
> +	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
> +		return -EINVAL;
> +
> +	/* Collect the command code byte */
> +	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
> +
> +	/* Send block of data to the device */
> +	return idt_smb_safe(write_block, pdev->client, ccode, seq->bytecnt,
> +		seq->data);
> +}
> +
> +/*
> + * idt_smb_read_block() - SMBus read method when I2C_SMBUS_BLOCK_DATA
> + *                        operation is available
> + * @pdev:	Pointer to the driver data
> + * @seq:	Buffer to read data to
> + */
> +static int idt_smb_read_block(struct idt_89hpesx_dev *pdev,
> +			      struct idt_smb_seq *seq)
> +{
> +	s32 sts;
> +	u8 ccode;
> +
> +	/* Return error if too much data passed to send */
> +	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
> +		return -EINVAL;
> +
> +	/* Collect the command code byte */
> +	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
> +
> +	/* Read block of data from the device */
> +	sts = idt_smb_safe(read_block, pdev->client, ccode, seq->data);
> +	if (sts != seq->bytecnt)
> +		return (sts < SUCCESS ? sts : -ENODATA);
> +
> +	return SUCCESS;
> +}
> +
> +/*
> + * idt_smb_write_i2c_block() - SMBus write method when I2C_SMBUS_I2C_BLOCK_DATA
> + *                             operation is available
> + * @pdev:	Pointer to the driver data
> + * @seq:	Sequence of data to be written
> + *
> + * NOTE It's usual SMBus write block operation, except the actual data length is
> + * sent as first byte of data
> + */
> +static int idt_smb_write_i2c_block(struct idt_89hpesx_dev *pdev,
> +				   const struct idt_smb_seq *seq)
> +{
> +	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
> +
> +	/* Return error if too much data passed to send */
> +	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
> +		return -EINVAL;
> +
> +	/* Collect the data to send. Length byte must be added prior the data */
> +	buf[0] = seq->bytecnt;
> +	memcpy(&buf[1], seq->data, seq->bytecnt);
> +
> +	/* Collect the command code byte */
> +	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
> +
> +	/* Send length and block of data to the device */
> +	return idt_smb_safe(write_i2c_block, pdev->client, ccode,
> +		seq->bytecnt + 1, buf);
> +}
> +
> +/*
> + * idt_smb_read_i2c_block() - SMBus read method when I2C_SMBUS_I2C_BLOCK_DATA
> + *                            operation is available
> + * @pdev:	Pointer to the driver data
> + * @seq:	Buffer to read data to
> + *
> + * NOTE It's usual SMBus read block operation, except the actual data length is
> + * retrieved as first byte of data
> + */
> +static int idt_smb_read_i2c_block(struct idt_89hpesx_dev *pdev,
> +				  struct idt_smb_seq *seq)
> +{
> +	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
> +	s32 sts;
> +
> +	/* Return error if too much data passed to send */
> +	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
> +		return -EINVAL;
> +
> +	/* Collect the command code byte */
> +	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
> +
> +	/* Read length and block of data from the device */
> +	sts = idt_smb_safe(read_i2c_block, pdev->client, ccode,
> +		seq->bytecnt + 1, buf);
> +	if (sts != seq->bytecnt + 1)
> +		return (sts < SUCCESS ? sts : -ENODATA);
> +	if (buf[0] != seq->bytecnt)
> +		return -ENODATA;
> +
> +	/* Copy retrieved data to the output data buffer */
> +	memcpy(seq->data, &buf[1], seq->bytecnt);
> +
> +	return SUCCESS;
> +}
> +
> +/*===========================================================================
> + *                          EEPROM IO-operations
> + *===========================================================================
> + */
> +
> +/*
> + * idt_eeprom_write() - EEPROM write operation
> + * @pdev:	Pointer to the driver data
> + * @memaddr:	Start EEPROM memory address
> + * @len:	Length of data to be written
> + * @data:	Data to be written to EEPROM
> + */
> +static int idt_eeprom_write(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
> +			    const u8 *data)
> +{
> +	struct idt_eeprom_seq eeseq;
> +	struct idt_smb_seq smbseq;
> +	int ret, retry;
> +	u16 idx;
> +
> +	/* Initialize SMBus sequence fields */
> +	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
> +	smbseq.data = (u8 *)&eeseq;
> +
> +	/* Send data byte-by-byte, checking if it is successfully written */
> +	for (idx = 0; idx < len; idx++, memaddr++) {
> +		/* Lock IDT SMBus device */
> +		mutex_lock(&pdev->smb_mtx);
> +
> +		/* Perform write operation */
> +		smbseq.bytecnt = EEPROM_WR_CNT;
> +		eeseq.cmd = pdev->inieecmd | EEPROM_OP_WRITE;
> +		eeseq.eeaddr = pdev->eeaddr;
> +		eeseq.memaddr = cpu_to_le16(memaddr);
> +		eeseq.data = data[idx];
> +		ret = pdev->smb_write(pdev, &smbseq);
> +		if (ret != SUCCESS) {
> +			dev_err_idt(pdev,
> +				"Failed to write 0x%04hx:0x%02hhx to eeprom",
> +				memaddr, data[idx]);
> +			goto err_mutex_unlock;
> +		}
> +
> +		/*
> +		 * Check whether the data is successfully written by reading
> +		 * from the same EEPROM memory address. Sometimes EEPROM may
> +		 * respond with NACK if it's busy with writing previous data,
> +		 * so we need to perform a few attempts of read cycle
> +		 */
> +		retry = RETRY_CNT;
> +		do {
> +			/* Send EEPROM memory address to read data from */
> +			smbseq.bytecnt = EEPROM_WRRD_CNT;
> +			eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
> +			ret = pdev->smb_write(pdev, &smbseq);
> +			if (ret != SUCCESS) {
> +				dev_err_idt(pdev,
> +					"Failed to init mem address 0x%02hhx",
> +					memaddr);
> +				goto err_mutex_unlock;
> +			}
> +
> +			/* Perform read operation */
> +			smbseq.bytecnt = EEPROM_RD_CNT;
> +			eeseq.data = ~data[idx];
> +			ret = pdev->smb_read(pdev, &smbseq);
> +			if (ret != SUCCESS) {
> +				dev_err_idt(pdev,
> +					"Failed to read mem address 0x%02hhx",
> +					memaddr);
> +				goto err_mutex_unlock;
> +			}
> +		} while (retry-- && (eeseq.cmd & EEPROM_NAERR));
> +
> +		/* Check whether IDT successfully sent data to EEPROM */
> +		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
> +			dev_err_idt(pdev, "Communication with EEPROM failed");
> +			ret = -EREMOTEIO;
> +			goto err_mutex_unlock;
> +		}
> +		if (eeseq.data != data[idx]) {
> +			dev_err_idt(pdev,
> +				"Values don't match 0x%02hhx != 0x%02hhx",
> +				eeseq.data, data[idx]);
> +			ret = -EREMOTEIO;
> +			goto err_mutex_unlock;
> +		}
> +
> +		/* Unlock IDT SMBus device */
> +err_mutex_unlock:
> +		mutex_unlock(&pdev->smb_mtx);
> +		if (ret != SUCCESS)
> +			return ret;
> +	}
> +
> +	return SUCCESS;
> +}
> +
> +/*
> + * idt_eeprom_read() - EEPROM read operation
> + * @pdev:	Pointer to the driver data
> + * @memaddr:	Start EEPROM memory address
> + * @len:	Length of data to read
> + * @buf:	Buffer to read data to
> + */
> +static int idt_eeprom_read(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
> +			   u8 *buf)
> +{
> +	struct idt_eeprom_seq eeseq;
> +	struct idt_smb_seq smbseq;
> +	u16 idx;
> +	int ret;
> +
> +	/* Initialize SMBus sequence fields */
> +	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
> +	smbseq.data = (u8 *)&eeseq;
> +
> +	/* Send data one-by-one, checking if it is successfully written */
> +	for (idx = 0; idx < len; idx++, memaddr++) {
> +		/* Lock IDT SMBus device */
> +		mutex_lock(&pdev->smb_mtx);
> +
> +		/* Send EEPROM memory address to read data from */
> +		smbseq.bytecnt = EEPROM_WRRD_CNT;
> +		eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
> +		eeseq.eeaddr = pdev->eeaddr;
> +		eeseq.memaddr = cpu_to_le16(memaddr);
> +		ret = pdev->smb_write(pdev, &smbseq);
> +		if (ret != SUCCESS) {
> +			dev_err_idt(pdev, "Failed to init mem address 0x%02hhx",
> +				memaddr);
> +			goto err_mutex_unlock;
> +		}
> +
> +		/* Perform read operation (rest of fields stay the same) */
> +		smbseq.bytecnt = EEPROM_RD_CNT;
> +		ret = pdev->smb_read(pdev, &smbseq);
> +		if (ret != SUCCESS) {
> +			dev_err_idt(pdev,
> +				"Failed to read eeprom address 0x%02hhx",
> +				memaddr);
> +			ret = -EREMOTEIO;
> +			goto err_mutex_unlock;
> +		}
> +
> +		/* Check whether IDT successfully read data from EEPROM */
> +		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
> +			dev_err_idt(pdev, "Communication with eeprom failed");
> +			ret = -EREMOTEIO;
> +			goto err_mutex_unlock;
> +		}
> +
> +		/* Save retrieved data */
> +		buf[idx] = eeseq.data;
> +
> +		/* Unlock IDT SMBus device */
> +err_mutex_unlock:
> +		mutex_unlock(&pdev->smb_mtx);
> +		if (ret != SUCCESS)
> +			return ret;
> +	}
> +
> +	return SUCCESS;
> +}
> +
> +/*===========================================================================
> + *                          CSR IO-operations
> + *===========================================================================
> + */
> +
> +/*
> + * idt_csr_write() - CSR write operation
> + * @pdev:	Pointer to the driver data
> + * @csraddr:	CSR address (with no two LS bits)
> + * @data:	Data to be written to CSR
> + */
> +static int idt_csr_write(struct idt_89hpesx_dev *pdev, u16 csraddr,
> +			 const u32 data)
> +{
> +	struct idt_csr_seq csrseq;
> +	struct idt_smb_seq smbseq;
> +	int ret;
> +
> +	/* Initialize SMBus sequence fields */
> +	smbseq.ccode = pdev->iniccode | CCODE_CSR;
> +	smbseq.data = (u8 *)&csrseq;
> +
> +	/* Lock IDT SMBus device */
> +	mutex_lock(&pdev->smb_mtx);
> +
> +	/* Perform write operation */
> +	smbseq.bytecnt = CSR_WR_CNT;
> +	csrseq.cmd = pdev->inicsrcmd | CSR_OP_WRITE;
> +	csrseq.csraddr = cpu_to_le16(csraddr);
> +	csrseq.data = cpu_to_le32(data);
> +	ret = pdev->smb_write(pdev, &smbseq);
> +	if (ret != SUCCESS) {
> +		dev_err_idt(pdev, "Failed to write 0x%04x: 0x%04x to csr",
> +			CSR_REAL_ADDR(csraddr), data);
> +		goto err_mutex_unlock;
> +	}
> +
> +	/* Send CSR address to read data from */
> +	smbseq.bytecnt = CSR_WRRD_CNT;
> +	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
> +	ret = pdev->smb_write(pdev, &smbseq);
> +	if (ret != SUCCESS) {
> +		dev_err_idt(pdev, "Failed to init csr address 0x%04x",
> +			CSR_REAL_ADDR(csraddr));
> +		goto err_mutex_unlock;
> +	}
> +
> +	/* Perform read operation */
> +	smbseq.bytecnt = CSR_RD_CNT;
> +	ret = pdev->smb_read(pdev, &smbseq);
> +	if (ret != SUCCESS) {
> +		dev_err_idt(pdev, "Failed to read csr 0x%04x",
> +			CSR_REAL_ADDR(csraddr));
> +		goto err_mutex_unlock;
> +	}
> +
> +	/* Check whether IDT successfully retrieved CSR data */
> +	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
> +		dev_err_idt(pdev, "IDT failed to perform CSR r/w");
> +		ret = -EREMOTEIO;
> +		goto err_mutex_unlock;
> +	}
> +
> +	/* Unlock IDT SMBus device */
> +err_mutex_unlock:
> +	mutex_unlock(&pdev->smb_mtx);
> +
> +	return ret;
> +}
> +
> +/*
> + * idt_csr_read() - CSR read operation
> + * @pdev:	Pointer to the driver data
> + * @csraddr:	CSR address (with no two LS bits)
> + * @data:	Data to be written to CSR
> + */
> +static int idt_csr_read(struct idt_89hpesx_dev *pdev, u16 csraddr, u32 *data)
> +{
> +	struct idt_csr_seq csrseq;
> +	struct idt_smb_seq smbseq;
> +	int ret;
> +
> +	/* Initialize SMBus sequence fields */
> +	smbseq.ccode = pdev->iniccode | CCODE_CSR;
> +	smbseq.data = (u8 *)&csrseq;
> +
> +	/* Lock IDT SMBus device */
> +	mutex_lock(&pdev->smb_mtx);
> +
> +	/* Send CSR register address before reading it */
> +	smbseq.bytecnt = CSR_WRRD_CNT;
> +	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
> +	csrseq.csraddr = cpu_to_le16(csraddr);
> +	ret = pdev->smb_write(pdev, &smbseq);
> +	if (ret != SUCCESS) {
> +		dev_err_idt(pdev, "Failed to init csr address 0x%04x",
> +			CSR_REAL_ADDR(csraddr));
> +		goto err_mutex_unlock;
> +	}
> +
> +	/* Perform read operation */
> +	smbseq.bytecnt = CSR_RD_CNT;
> +	ret = pdev->smb_read(pdev, &smbseq);
> +	if (ret != SUCCESS) {
> +		dev_err_idt(pdev, "Failed to read csr 0x%04hx",
> +			CSR_REAL_ADDR(csraddr));
> +		goto err_mutex_unlock;
> +	}
> +
> +	/* Check whether IDT successfully retrieved CSR data */
> +	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
> +		dev_err_idt(pdev, "IDT failed to perform CSR r/w");
> +		ret = -EREMOTEIO;
> +		goto err_mutex_unlock;
> +	}
> +
> +	/* Save data retrieved from IDT */
> +	*data = le32_to_cpu(csrseq.data);
> +
> +	/* Unlock IDT SMBus device */
> +err_mutex_unlock:
> +	mutex_unlock(&pdev->smb_mtx);
> +
> +	return ret;
> +}
> +
> +/*===========================================================================
> + *                          Sysfs-nodes IO-operations
> + *===========================================================================
> + */
> +
> +/*
> + * idt_sysfs_eeprom_write() - EEPROM sysfs-node write callback
> + * @filep:	Pointer to the file system node
> + * @kobj:	Pointer to the kernel object related to the sysfs-node
> + * @attr:	Attributes of the file
> + * @buf:	Buffer to write data to
> + * @off:	Offset at which data should be written to
> + * @count:	Number of bytes to write
> + */
> +static ssize_t idt_sysfs_eeprom_write(struct file *filp, struct kobject *kobj,
> +				      struct bin_attribute *attr,
> +				      char *buf, loff_t off, size_t count)
> +{
> +	struct idt_89hpesx_dev *pdev;
> +	int ret;
> +
> +	/* Retrieve driver data */
> +	pdev = to_pdev_kobj(kobj);
> +
> +	/* Perform EEPROM write operation */
> +	ret = idt_eeprom_write(pdev, (u16)off, (u16)count, (u8 *)buf);
> +	return (ret != SUCCESS ? ret : count);
> +}
> +
> +/*
> + * idt_sysfs_eeprom_read() - EEPROM sysfs-node read callback
> + * @filep:	Pointer to the file system node
> + * @kobj:	Pointer to the kernel object related to the sysfs-node
> + * @attr:	Attributes of the file
> + * @buf:	Buffer to write data to
> + * @off:	Offset at which data should be written to
> + * @count:	Number of bytes to write
> + */
> +static ssize_t idt_sysfs_eeprom_read(struct file *filp, struct kobject *kobj,
> +				     struct bin_attribute *attr,
> +				     char *buf, loff_t off, size_t count)
> +{
> +	struct idt_89hpesx_dev *pdev;
> +	int ret;
> +
> +	/* Retrieve driver data */
> +	pdev = to_pdev_kobj(kobj);
> +
> +	/* Perform EEPROM read operation */
> +	ret = idt_eeprom_read(pdev, (u16)off, (u16)count, (u8 *)buf);
> +	return (ret != SUCCESS ? ret : count);
> +}
> +
> +/*
> + * idt_sysfs_csr_store() - CSR sysfs-node write callback
> + * @kobj:	Pointer to the kernel object related to the sysfs-node
> + * @attr:	Attributes of the file
> + * @buf:	Buffer to write data to
> + * @count:	Size of the buffer
> + *
> + * It accepts either "0x<reg addr>:0x<value>" for saving register address
> + * and writing value to specified DWORD register or "0x<reg addr>" for
> + * just saving register address in order to perform next read operation.
> + *
> + * WARNING No spaces are allowed. Incoming string must be strictly formated as:
> + * "<reg addr>:<value>". Register address must be aligned within 4 bytes
> + * (one DWORD).
> + */
> +static ssize_t idt_sysfs_csr_store(struct device *dev,
> +				   struct device_attribute *attr,
> +				   const char *buf, size_t count)
> +{
> +	struct idt_89hpesx_dev *pdev;
> +	char *colon_ch, *csraddr_str, *csrval_str;
> +	int ret, csraddr_len, csrval_len;
> +	u32 csraddr, csrval;
> +
> +	/* Retrieve driver data */
> +	pdev = dev_get_drvdata(dev);
> +
> +	/* Find position of colon in the buffer */
> +	colon_ch = strnchr(buf, count, ':');
> +
> +	/*
> +	 * If there is colon passed then new CSR value should be parsed as
> +	 * well, so allocate buffer for CSR address substring.
> +	 * If no colon is found, then string must have just one number with
> +	 * no new CSR value
> +	 */
> +	if (colon_ch != NULL) {
> +		csraddr_len = colon_ch - buf;
> +		csraddr_str =
> +			kmalloc(sizeof(char)*(csraddr_len + 1), GFP_KERNEL);
> +		if (csraddr_str == NULL)
> +			return -ENOMEM;
> +		/* Copy the register address to the substring buffer */
> +		strncpy(csraddr_str, buf, csraddr_len);
> +		csraddr_str[csraddr_len] = '\0';
> +		/* Register value must follow the colon */
> +		csrval_str = colon_ch + 1;
> +		csrval_len = strnlen(csrval_str, count - csraddr_len);
> +	} else /* if (str_colon == NULL) */ {
> +		csraddr_str = (char *)buf; /* Just to shut warning up */
> +		csraddr_len = strnlen(csraddr_str, count);
> +		csrval_str = NULL;
> +		csrval_len = 0;
> +	}
> +
> +	/* Convert CSR address to u32 value */
> +	ret = kstrtou32(csraddr_str, 0, &csraddr);
> +	if (ret != SUCCESS)
> +		goto free_csraddr_str;
> +
> +	/* Check whether passed register address is valid */
> +	if (csraddr > CSR_MAX || !IS_ALIGNED(csraddr, SZ_4)) {
> +		ret = -EINVAL;
> +		goto free_csraddr_str;
> +	}
> +
> +	/* Shift register address to the right so to have u16 address */
> +	csraddr >>= 2;
> +
> +	/* Parse new CSR value and send it to IDT, if colon has been found */
> +	if (colon_ch != NULL) {
> +		ret = kstrtou32(csrval_str, 0, &csrval);
> +		if (ret != SUCCESS)
> +			goto free_csraddr_str;
> +
> +		ret = idt_csr_write(pdev, (u16)csraddr, csrval);
> +		if (ret != SUCCESS)
> +			goto free_csraddr_str;
> +	}
> +
> +	/* Save CSR address in the data structure for future read operations */
> +	atomic_set(&pdev->csr, (int)csraddr);
> +
> +	/* Free memory only if colon has been found */
> +free_csraddr_str:
> +	if (colon_ch != NULL)
> +		kfree(csraddr_str);
> +
> +	return (ret != SUCCESS ? ret : count);
> +}
> +
> +/*
> + * idt_sysfs_csr_show() - CSR sysfs-node read callback
> + * @kobj:	Pointer to the kernel object related to the sysfs-node
> + * @attr:	Attributes of the file
> + * @buf:	Buffer to write data to
> + *
> + * It just prints the pair "0x<reg addr>:0x<value>" to passed buffer.
> + */
> +static ssize_t idt_sysfs_csr_show(struct device *dev,
> +				  struct device_attribute *attr, char *buf)
> +{
> +	struct idt_89hpesx_dev *pdev;
> +	u32 csraddr, csrval;
> +	int ret;
> +
> +	/* Retrieve driver data */
> +	pdev = dev_get_drvdata(dev);
> +
> +	/* Read current CSR address */
> +	csraddr = atomic_read(&pdev->csr);
> +
> +	/* Perform CSR read operation */
> +	ret = idt_csr_read(pdev, (u16)csraddr, &csrval);
> +	if (ret != SUCCESS)
> +		return ret;
> +
> +	/* Shift register address to the left so to have real address */
> +	csraddr <<= 2;
> +
> +	/* Print the "0x<reg addr>:0x<value>" to buffer */
> +	return snprintf(buf, PAGE_SIZE, "0x%05x:0x%08x\n",
> +		(unsigned int)csraddr, (unsigned int)csrval);
> +}
> +
> +/*
> + * eeprom_attribute - EEPROM sysfs-node attributes
> + *
> + * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
> + * be read-only as well if the corresponding flag is specified in OF node.
> + */
> +static struct bin_attribute eeprom_attribute = {
> +	.attr = {
> +		.name = "eeprom",
> +		.mode = S_IRUGO | S_IWUSR
> +	},
> +	.size = EEPROM_DEF_SIZE,
> +	.write = idt_sysfs_eeprom_write,
> +	.read = idt_sysfs_eeprom_read
> +};
> +
> +/*
> + * csr_attribute - CSR sysfs-node attributes
> + */
> +static struct device_attribute csr_attribute = {
> +	.attr = {
> +		.name = "csr",
> +		.mode = S_IRUGO | S_IWUSR
> +	},
> +	.store = idt_sysfs_csr_store,
> +	.show = idt_sysfs_csr_show,
> +};

Note, for the future, just use DEVICE_ATTR_RW() for stuff like this.

But convert it to debugfs please.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH] Add IDT 89HPESx EEPROM/CSR driver
  2016-10-30 13:53 ` Greg KH
@ 2016-11-24 18:42   ` Serge Semin
  0 siblings, 0 replies; 34+ messages in thread
From: Serge Semin @ 2016-11-24 18:42 UTC (permalink / raw)
  To: Greg KH; +Cc: arnd, wsa, jdelvare, linux-i2c, linux-kernel, Sergey.Semin

On Sun, Oct 30, 2016 at 09:53:58AM -0400, Greg KH <gregkh@linuxfoundation.org> wrote:

> On Mon, Oct 03, 2016 at 02:13:45AM +0300, Serge Semin wrote:
> > Hello linux folks,
> > 
> >     This driver primarily is developed to give an access to EEPROM of IDT
> > PCIe-switches. Such switches provide a simple SMBus interface to perform
> > IO-operations from/to EEPROM, which is located at private (also called master)
> > SMBus of the switches. Using that interface this driver creates a simple
> > binary sysfs-file in the device directory:
> > /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
> > In case if read-only flag is specified in the device dts-node, user-space
> > applications won't be able to write to the EEPROM sysfs-node.
> > 
> >     Additionally IDT 89HPESx SMBus interface provides an ability to write/read
> > data of device CSRs. This driver exposes corresponding sysfs-file to perform
> > simple IO operations using that facility for just basic debug purpose.
> 
> If it's for debugging, please put it in debugfs, not in sysfs.  sysfs
> files for one-off drivers is usually discouraged, but at the least, you
> have to document it in a Documentation/ABI/ file.  For debugfs files, we
> don't care, you can do whatever you want in them :)
> 

Undrestood. I'll move it to Debugfs.

> > Particularly next file is created in the device specific sysfs-directory:
> > /sys/bus/i2c/devices/<bus>-<devaddr>/csr
> > Format of the sysfs-node is:
> > $ cat /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
> > <CSR address>:<CSR value>
> > So reading the content of the sysfs-file gives current CSR address and
> > it value. If user-space application wishes to change current CSR address,
> > it can just write a proper value to the sysfs-file:
> > $ echo "<CSR address>" > /sys/bus/i2c/devices/<bus>-<devaddr>/csr
> > If it wants to change the CSR value as well, the format of the write
> > operation is:
> > $ echo "<CSR address>:<CSR value>" > \
> >         /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
> > CSR address and value can be any of hexadecimal, decimal or octal format.
> 
> Yeah, that all should go into debugfs.
> 

Ok.

> >     The driver supports the most of the commonly available SMBus operations:
> > SMBus i2c block, SMBus block, smbus word and byte. The code has been tested
> > to be built for x32/x64 MIPS architecture and tested on the x32 MIPS machine.
> > The patch was applied on top of commit c6935931c1894ff857616ff8549b61236a19148f
> > of master branch of repository
> > git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git
> > 
> > 
> > Thanks,
> > 
> > =============================
> > Serge V. Semin
> > Leading Programmer
> > Embedded SW development group
> > T-platforms
> > =============================
> > 
> > Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
> 
> meta-comment, the "hello", and "thanks" and signature doesn't need to be
> in a changelog text, next time you can drop that.  See the many kernel
> patches on the mailing lists for examples of how to do this.
> 

Understood.

> 
> > 
> > ---
> >  .../devicetree/bindings/misc/idt_89hpesx.txt       |   41 +
> 
> 
> Can you split this out into a separate file and be sure to cc: the patch
> to the devicetree maintainers?
> 
> >  drivers/misc/eeprom/Kconfig                        |   10 +
> >  drivers/misc/eeprom/Makefile                       |    1 +
> >  drivers/misc/eeprom/idt_89hpesx.c                  | 1483 ++++++++++++++++++++
> >  4 files changed, 1535 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> >  create mode 100644 drivers/misc/eeprom/idt_89hpesx.c
> > 
> > diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> > new file mode 100644
> > index 0000000..469cc93
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> > @@ -0,0 +1,41 @@
> > +EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
> > +
> > +Required properties:
> > +  - compatible : should be "<manufacturer>,<type>"
> > +		 Basically there is only one manufacturer: idt, but some
> > +		 compatible devices may be produced in future. Following devices
> > +		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
> > +		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
> > +		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
> > +		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
> > +		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
> > +		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
> > +		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
> > +		 89hpes64h16ag2;
> > +		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
> > +		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
> > +		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
> > +		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
> > +		 89hpes48t12, 89hpes48t12g2.
> > +		 Current implementation of the driver doesn't have any device-
> > +		 specific functionalities. But since each of them differs
> > +		 by registers mapping, CSRs read/write restrictions can be
> > +		 added in future.
> > +  - reg :	 I2C address of the IDT 89HPES device.
> > +
> > +Optional properties:
> > +  - read-only :	 Parameterless property disables writes to the EEPROM
> > +  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
> > +		 (default value is 4096 bytes if option isn't specified)
> > +  - idt,eeaddr : Custom address of EEPROM device
> > +		 (If not specified IDT 89HPESx device will try to communicate
> > +		  with EEPROM sited by default address - 0x50)
> > +
> > +Example:
> > +	idt_pcie_sw@60 {
> > +		compatible = "idt,89hpes12nt3";
> > +		reg = <0x60>;
> > +		read-only;
> > +		idt,eesize = <65536>;
> > +		idt,eeaddr = <0x50>;
> > +	};
> > diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
> > index c4e41c2..de58762 100644
> > --- a/drivers/misc/eeprom/Kconfig
> > +++ b/drivers/misc/eeprom/Kconfig
> > @@ -100,4 +100,14 @@ config EEPROM_DIGSY_MTC_CFG
> >  
> >  	  If unsure, say N.
> >  
> > +config EEPROM_IDT_89HPESX
> > +	tristate "IDT 89HPESx PCIe-swtiches EEPROM / CSR support"
> > +	depends on I2C && SYSFS
> > +	help
> > +	  Enable this driver to get read/write access to EEPROM / CSRs
> > +	  over IDT PCIe-swtich i2c-slave interface.
> > +
> > +	  This driver can also be built as a module. If so, the module
> > +	  will be called idt_89hpesx.
> > +
> >  endmenu
> > diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
> > index fc1e81d..90a5262 100644
> > --- a/drivers/misc/eeprom/Makefile
> > +++ b/drivers/misc/eeprom/Makefile
> > @@ -5,3 +5,4 @@ obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o
> >  obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
> >  obj-$(CONFIG_EEPROM_93XX46)	+= eeprom_93xx46.o
> >  obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
> > +obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
> > diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c
> > new file mode 100644
> > index 0000000..f95f4f0
> > --- /dev/null
> > +++ b/drivers/misc/eeprom/idt_89hpesx.c
> > @@ -0,0 +1,1483 @@
> > +/*
> > + *   This file is provided under a GPLv2 license.  When using or
> > + *   redistributing this file, you may do so under that license.
> > + *
> > + *   GPL LICENSE SUMMARY
> > + *
> > + *   Copyright (C) 2016 T-Platforms All Rights Reserved.
> > + *
> > + *   This program is free software; you can redistribute it and/or modify it
> > + *   under the terms and conditions of the GNU General Public License,
> > + *   version 2, as published by the Free Software Foundation.
> > + *
> > + *   This program is distributed in the hope that it will be useful, but WITHOUT
> > + *   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> > + *   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
> > + *   more details.
> > + *
> > + *   You should have received a copy of the GNU General Public License along
> > + *   with this program; if not, it can be found <http://www.gnu.org/licenses/>.
> > + *
> > + *   The full GNU General Public License is included in this distribution in
> > + *   the file called "COPYING".
> > + *
> > + *   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.
> > + *
> > + * IDT PCIe-switch NTB Linux driver
> > + *
> > + * Contact Information:
> > + * Serge Semin <fancer.lancer@gmail.com>, <Sergey.Semin@t-platforms.ru>
> > + */
> > +/*
> > + *           NOTE of the IDT 89HPESx SMBus-slave interface driver
> > + *    This driver primarily is developed to have an access to EEPROM device of
> > + * IDT PCIe-switches. IDT provides a simple SMBus interface to perform IO-
> > + * operations from/to EEPROM, which is located at private (so called Master)
> > + * SMBus of switches. Using that interface this the driver creates a simple
> > + * binary sysfs-file in the device directory:
> > + * /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
> > + * In case if read-only flag is specified in the dts-node of device desription,
> > + * User-space applications won't be able to write to the EEPROM sysfs-node.
> > + *    Additionally IDT 89HPESx SMBus interface has an ability to write/read
> > + * data of device CSRs. This driver exposes another sysfs-file to perform
> > + * simple IO operations using that ability for just basic debug purpose.
> > + * Particularly next file is created in the device specific sysfs-directory:
> > + * /sys/bus/i2c/devices/<bus>-<devaddr>/csr
> > + * Format of the sysfs-node is:
> > + * $ cat /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
> > + * <CSR address>:<CSR value>
> > + * So reading the content of the sysfs-file gives current CSR address and
> > + * it value. If User-space application wishes to change current CSR address,
> > + * it can just write a proper value to the sysfs-file:
> > + * $ echo "<CSR address>" > /sys/bus/i2c/devices/<bus>-<devaddr>/csr
> > + * If it wants to change the CSR value as well, the format of the write
> > + * operation is:
> > + * $ echo "<CSR address>:<CSR value>" > \
> > + *        /sys/bus/i2c/devices/<bus>-<devaddr>/csr;
> > + * CSR address and value can be any of hexadecimal, decimal or octal format.
> > + */
> > +
> > +/*
> > + * Note: You can load this module with either option 'dyndbg=+p' or define the
> > + * next preprocessor constant
> 
> What does this option do?  I don't understand it.
> 
> > + */
> > +/* #define DEBUG */
> 
> Ah, just drop all of this, dynamic debugging is known how to be used
> through the whole kernel, no need to document it again :)
> 

Ok. I'll drop all of this down.

> > +
> > +#include <linux/kernel.h>
> > +#include <linux/init.h>
> > +#include <linux/module.h>
> > +#include <linux/types.h>
> > +#include <linux/sizes.h>
> > +#include <linux/slab.h>
> > +#include <linux/mutex.h>
> > +#include <linux/sysfs.h>
> > +#include <linux/mod_devicetable.h>
> > +#include <linux/of.h>
> > +#include <linux/i2c.h>
> > +#include <linux/pci_ids.h>
> 
> why pci_ids.h?
> 

I need to have PCI ids to probe the IDT device. As I already said IDT PCIe
switch has a separate i2c slave interface with an access to all device CSRs,
which are basically PCI configuration spaces of all switch ports. So when
device is probed, the driver reads VID and DID to make sure, that it is
really connected to IDT switch.

> > +
> > +#define IDT_NAME		"89hpesx"
> > +#define IDT_89HPESX_DESC	"IDT 89HPESx SMBus-slave interface driver"
> > +#define IDT_89HPESX_VER		"1.0"
> > +
> > +MODULE_DESCRIPTION(IDT_89HPESX_DESC);
> > +MODULE_VERSION(IDT_89HPESX_VER);
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_AUTHOR("T-platforms");
> > +
> > +/*
> > + * Some common constant used in the driver for better readability:
> > + * @SUCCESS:	Success of a function execution
> > + */
> > +#define SUCCESS (0)
> 
> No need to define this, we all know that 0 is "success", this is the
> kernel.
> 

Ok, I'll delete it from the code.

> > +
> > +/*
> > + * struct idt_89hpesx_dev - IDT 89HPESx device data structure
> > + * @eesize:	Size of EEPROM in bytes (calculated from "idt,eecompatible")
> > + * @eero:	EEPROM Read-only flag
> > + * @eeaddr:	EEPROM custom address
> > + *
> > + * @inieecmd:	Initial cmd value for EEPROM read/write operations
> > + * @inicsrcmd:	Initial cmd value for CSR read/write operations
> > + * @iniccode:	Initialial command code value for IO-operations
> > + *
> > + * @csr:	CSR address to perform read operation
> > + *
> > + * @smb_write:	SMBus write method
> > + * @smb_read:	SMBus read method
> > + * @smb_mtx:	SMBus mutex
> > + *
> > + * @client:	i2c client used to perform IO operations
> > + *
> > + * @eenode:	EEPROM sysfs-node to read/write data to/from EEPROM
> > + * @regnode:	Register sysfs-node to read/write CSRs
> > + */
> > +struct idt_smb_seq;
> > +struct idt_89hpesx_dev {
> > +	u32 eesize;
> > +	bool eero;
> > +	u8 eeaddr;
> > +
> > +	u8 inieecmd;
> > +	u8 inicsrcmd;
> > +	u8 iniccode;
> > +
> > +	atomic_t csr;
> > +
> > +	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
> > +	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
> > +	struct mutex smb_mtx;
> > +
> > +	struct i2c_client *client;
> > +
> > +	struct bin_attribute eenode;
> > +	struct bin_attribute regnode;
> > +};
> > +#define to_pdev_kobj(__kobj) \
> > +	dev_get_drvdata(container_of(__kobj, struct device, kobj))
> > +
> > +/*
> > + * struct idt_smb_seq - sequence of data to be read/written from/to IDT 89HPESx
> > + * @ccode:	SMBus command code
> > + * @bytecnt:	Byte count of operation
> > + * @data:	Data to by written
> > + */
> > +struct idt_smb_seq {
> > +	u8 ccode;
> > +	u8 bytecnt;
> > +	u8 *data;
> > +};
> > +
> > +/*
> > + * struct idt_eeprom_seq - sequence of data to be read/written from/to EEPROM
> > + * @cmd:	Transaction CMD
> > + * @eeaddr:	EEPROM custom address
> > + * @memaddr:	Internal memory address of EEPROM
> > + * @data:	Data to be written at the memory address
> > + */
> > +struct idt_eeprom_seq {
> > +	u8 cmd;
> > +	u8 eeaddr;
> > +	u16 memaddr;
> 
> endian?  Is this big or little?
> 

Since the IDT-switch is PCIe device, it's little endian. Of course, the code
does the proper conversions. You can see it in the functions:
idt_eeprom_write()/idt_eeprom_read().

> > +	u8 data;
> > +} __packed;
> > +
> > +/*
> > + * struct idt_csr_seq - sequence of data to be read/written from/to CSR
> > + * @cmd:	Transaction CMD
> > + * @csraddr:	Internal IDT device CSR address
> > + * @data:	Data to be read/written from/to the CSR address
> > + */
> > +struct idt_csr_seq {
> > +	u8 cmd;
> > +	u16 csraddr;
> > +	u32 data;
> 
> Same for these, what endian are they?
> 
> > +} __packed;
> > +
> > +/*
> > + * SMBus command code macros
> > + * @CCODE_END:		Indicates the end of transaction
> > + * @CCODE_START:	Indicates the start of transaction
> > + * @CCODE_CSR:		CSR read/write transaction
> > + * @CCODE_EEPROM:	EEPROM read/write transaction
> > + * @CCODE_BYTE:		Supplied data has BYTE length
> > + * @CCODE_WORD:		Supplied data has WORD length
> > + * @CCODE_BLOCK:	Supplied data has variable length passed in bytecnt
> > + *			byte right following CCODE byte
> > + */
> > +#define CCODE_END	((u8)0x01)
> > +#define CCODE_START	((u8)0x02)
> > +#define CCODE_CSR	((u8)0x00)
> > +#define CCODE_EEPROM	((u8)0x04)
> > +#define CCODE_BYTE	((u8)0x00)
> > +#define CCODE_WORD	((u8)0x20)
> > +#define CCODE_BLOCK	((u8)0x40)
> > +#define CCODE_PEC	((u8)0x80)
> > +
> > +/*
> > + * EEPROM command macros
> > + * @EEPROM_OP_WRITE:	EEPROM write operation
> > + * @EEPROM_OP_READ:	EEPROM read operation
> > + * @EEPROM_USA:		Use specified address of EEPROM
> > + * @EEPROM_NAERR:	EEPROM device is not ready to respond
> > + * @EEPROM_LAERR:	EEPROM arbitration loss error
> > + * @EEPROM_MSS:		EEPROM misplace start & stop bits error
> > + * @EEPROM_WR_CNT:	Bytes count to perform write operation
> > + * @EEPROM_WRRD_CNT:	Bytes count to write before reading
> > + * @EEPROM_RD_CNT:	Bytes count to perform read operation
> > + * @EEPROM_DEF_SIZE:	Fall back size of EEPROM
> > + * @EEPROM_DEF_ADDR:	Defatul EEPROM address
> > + */
> > +#define EEPROM_OP_WRITE	((u8)0x00)
> > +#define EEPROM_OP_READ	((u8)0x01)
> > +#define EEPROM_USA	((u8)0x02)
> > +#define EEPROM_NAERR	((u8)0x08)
> > +#define EEPROM_LAERR    ((u8)0x10)
> > +#define EEPROM_MSS	((u8)0x20)
> > +#define EEPROM_WR_CNT	((u8)5)
> > +#define EEPROM_WRRD_CNT	((u8)4)
> > +#define EEPROM_RD_CNT	((u8)5)
> > +#define EEPROM_DEF_SIZE	((u16)4096)
> > +#define EEPROM_DEF_ADDR	((u8)0x50)
> > +
> > +/*
> > + * CSR command macros
> > + * @CSR_DWE:		Enable all four bytes of the operation
> > + * @CSR_OP_WRITE:	CSR write operation
> > + * @CSR_OP_READ:	CSR read operation
> > + * @CSR_RERR:		Read operation error
> > + * @CSR_WERR:		Write operation error
> > + * @CSR_WR_CNT:		Bytes count to perform write operation
> > + * @CSR_WRRD_CNT:	Bytes count to write before reading
> > + * @CSR_RD_CNT:		Bytes count to perform read operation
> > + * @CSR_MAX:		Maximum CSR address
> > + * @CSR_DEF:		Default CSR address
> > + * @CSR_REAL_ADDR:	CSR real unshifted address
> > + */
> > +#define CSR_DWE			((u8)0x0F)
> > +#define CSR_OP_WRITE		((u8)0x00)
> > +#define CSR_OP_READ		((u8)0x10)
> > +#define CSR_RERR		((u8)0x40)
> > +#define CSR_WERR		((u8)0x80)
> > +#define CSR_WR_CNT		((u8)7)
> > +#define CSR_WRRD_CNT		((u8)3)
> > +#define CSR_RD_CNT		((u8)7)
> > +#define CSR_MAX			((u32)0x3FFFF)
> > +#define CSR_DEF			((u16)0x0000)
> > +#define CSR_REAL_ADDR(val)	((unsigned int)val << 2)
> > +
> > +/*
> > + * IDT 89HPESx basic register
> > + * @IDT_VIDDID_CSR:	PCIe VID and DID of IDT 89HPESx
> > + * @IDT_VID_MASK:	Mask of VID
> > + */
> > +#define IDT_VIDDID_CSR	((u32)0x0000)
> > +#define IDT_VID_MASK	((u32)0xFFFF)
> > +
> > +/*
> > + * IDT 89HPESx can send NACK when new command is sent before previous one
> > + * fininshed execution. In this case driver retries operation
> > + * certain times.
> > + * @RETRY_CNT:		Number of retries before giving up and fail
> > + * @idt_smb_safe:	Generate a retry loop on corresponding SMBus method
> > + */
> > +#define RETRY_CNT (128)
> > +#define idt_smb_safe(ops, args...) ({ \
> > +	int __retry = RETRY_CNT; \
> > +	s32 __sts; \
> > +	do { \
> > +		__sts = i2c_smbus_ ## ops ## _data(args); \
> > +	} while (__retry-- && __sts < SUCCESS); \
> > +	__sts; \
> > +})
> > +
> > +/*
> > + * Wrapper dev_err/dev_warn/dev_info/dev_dbg macros
> > + */
> > +#define dev_err_idt(pdev, args...) \
> > +	dev_err(&pdev->client->dev, ## args)
> > +#define dev_warn_idt(pdev, args...) \
> > +	dev_warn(&pdev->client->dev, ## args)
> > +#define dev_info_idt(pdev, args...) \
> > +	dev_info(&pdev->client->dev, ## args)
> > +#define dev_dbg_idt(pdev, args...) \
> > +	dev_dbg(&pdev->client->dev, ## args)
> 
> ick, don't wrap them, just use them as-is, it's not that hard to write
> them out please.
> 

Ok.

> > +
> > +/*===========================================================================
> > + *                         i2c bus level IO-operations
> > + *===========================================================================
> > + */
> > +
> > +/*
> > + * idt_smb_write_byte() - SMBus write method when I2C_SMBUS_BYTE_DATA operation
> > + *                        is only available
> > + * @pdev:	Pointer to the driver data
> > + * @seq:	Sequence of data to be written
> > + */
> > +static int idt_smb_write_byte(struct idt_89hpesx_dev *pdev,
> > +			      const struct idt_smb_seq *seq)
> > +{
> > +	s32 sts;
> > +	u8 ccode;
> > +	int idx;
> > +
> > +	/* Loop over the supplied data sending byte one-by-one */
> > +	for (idx = 0; idx < seq->bytecnt; idx++) {
> > +		/* Collect the command code byte */
> > +		ccode = seq->ccode | CCODE_BYTE;
> > +		if (idx == 0)
> > +			ccode |= CCODE_START;
> > +		if (idx == seq->bytecnt - 1)
> > +			ccode |= CCODE_END;
> > +
> > +		/* Send data to the device */
> > +		sts = idt_smb_safe(write_byte, pdev->client, ccode,
> > +			seq->data[idx]);
> > +		if (sts != SUCCESS)
> > +			return (int)sts;
> > +	}
> > +
> > +	return SUCCESS;
> > +}
> > +
> > +/*
> > + * idt_smb_read_byte() - SMBus read method when I2C_SMBUS_BYTE_DATA operation
> > + *                        is only available
> > + * @pdev:	Pointer to the driver data
> > + * @seq:	Buffer to read data to
> > + */
> > +static int idt_smb_read_byte(struct idt_89hpesx_dev *pdev,
> > +			     struct idt_smb_seq *seq)
> > +{
> > +	s32 sts;
> > +	u8 ccode;
> > +	int idx;
> > +
> > +	/* Loop over the supplied buffer receiving byte one-by-one */
> > +	for (idx = 0; idx < seq->bytecnt; idx++) {
> > +		/* Collect the command code byte */
> > +		ccode = seq->ccode | CCODE_BYTE;
> > +		if (idx == 0)
> > +			ccode |= CCODE_START;
> > +		if (idx == seq->bytecnt - 1)
> > +			ccode |= CCODE_END;
> > +
> > +		/* Read data from the device */
> > +		sts = idt_smb_safe(read_byte, pdev->client, ccode);
> > +		if (sts < SUCCESS)
> > +			return (int)sts;
> > +
> > +		seq->data[idx] = (u8)sts;
> > +	}
> > +
> > +	return SUCCESS;
> > +}
> > +
> > +/*
> > + * idt_smb_write_word() - SMBus write method when I2C_SMBUS_BYTE_DATA and
> > + *                        I2C_FUNC_SMBUS_WORD_DATA operations are available
> > + * @pdev:	Pointer to the driver data
> > + * @seq:	Sequence of data to be written
> > + */
> > +static int idt_smb_write_word(struct idt_89hpesx_dev *pdev,
> > +			      const struct idt_smb_seq *seq)
> > +{
> > +	s32 sts;
> > +	u8 ccode;
> > +	int idx, evencnt;
> > +
> > +	/* Calculate the even count of data to send */
> > +	evencnt = seq->bytecnt - (seq->bytecnt % 2);
> > +
> > +	/* Loop over the supplied data sending two bytes at a time */
> > +	for (idx = 0; idx < evencnt; idx += 2) {
> > +		/* Collect the command code byte */
> > +		ccode = seq->ccode | CCODE_WORD;
> > +		if (idx == 0)
> > +			ccode |= CCODE_START;
> > +		if (idx == evencnt - 2)
> > +			ccode |= CCODE_END;
> > +
> > +		/* Send word data to the device */
> > +		sts = idt_smb_safe(write_word, pdev->client, ccode,
> > +			*(u16 *)&seq->data[idx]);
> > +		if (sts != SUCCESS)
> > +			return (int)sts;
> > +	}
> > +
> > +	/* If there is odd number of bytes then send just one last byte */
> > +	if (seq->bytecnt != evencnt) {
> > +		/* Collect the command code byte */
> > +		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
> > +		if (idx == 0)
> > +			ccode |= CCODE_START;
> > +
> > +		/* Send byte data to the device */
> > +		sts = idt_smb_safe(write_byte, pdev->client, ccode,
> > +			seq->data[idx]);
> > +		if (sts != SUCCESS)
> > +			return (int)sts;
> > +	}
> > +
> > +	return SUCCESS;
> > +}
> > +
> > +/*
> > + * idt_smb_read_word() - SMBus read method when I2C_SMBUS_BYTE_DATA and
> > + *                       I2C_FUNC_SMBUS_WORD_DATA operations are available
> > + * @pdev:	Pointer to the driver data
> > + * @seq:	Buffer to read data to
> > + */
> > +static int idt_smb_read_word(struct idt_89hpesx_dev *pdev,
> > +			     struct idt_smb_seq *seq)
> > +{
> > +	s32 sts;
> > +	u8 ccode;
> > +	int idx, evencnt;
> > +
> > +	/* Calculate the even count of data to send */
> > +	evencnt = seq->bytecnt - (seq->bytecnt % 2);
> > +
> > +	/* Loop over the supplied data reading two bytes at a time */
> > +	for (idx = 0; idx < evencnt; idx += 2) {
> > +		/* Collect the command code byte */
> > +		ccode = seq->ccode | CCODE_WORD;
> > +		if (idx == 0)
> > +			ccode |= CCODE_START;
> > +		if (idx == evencnt - 2)
> > +			ccode |= CCODE_END;
> > +
> > +		/* Read word data from the device */
> > +		sts = idt_smb_safe(read_word, pdev->client, ccode);
> > +		if (sts < SUCCESS)
> > +			return (int)sts;
> > +
> > +		*(u16 *)&seq->data[idx] = (u16)sts;
> > +	}
> > +
> > +	/* If there is odd number of bytes then receive just one last byte */
> > +	if (seq->bytecnt != evencnt) {
> > +		/* Collect the command code byte */
> > +		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
> > +		if (idx == 0)
> > +			ccode |= CCODE_START;
> > +
> > +		/* Read last data byte from the device */
> > +		sts = idt_smb_safe(read_byte, pdev->client, ccode);
> > +		if (sts < SUCCESS)
> > +			return (int)sts;
> > +
> > +		seq->data[idx] = (u8)sts;
> > +	}
> > +
> > +	return SUCCESS;
> > +}
> > +
> > +/*
> > + * idt_smb_write_block() - SMBus write method when I2C_SMBUS_BLOCK_DATA
> > + *                         operation is available
> > + * @pdev:	Pointer to the driver data
> > + * @seq:	Sequence of data to be written
> > + */
> > +static int idt_smb_write_block(struct idt_89hpesx_dev *pdev,
> > +			       const struct idt_smb_seq *seq)
> > +{
> > +	u8 ccode;
> > +
> > +	/* Return error if too much data passed to send */
> > +	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
> > +		return -EINVAL;
> > +
> > +	/* Collect the command code byte */
> > +	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
> > +
> > +	/* Send block of data to the device */
> > +	return idt_smb_safe(write_block, pdev->client, ccode, seq->bytecnt,
> > +		seq->data);
> > +}
> > +
> > +/*
> > + * idt_smb_read_block() - SMBus read method when I2C_SMBUS_BLOCK_DATA
> > + *                        operation is available
> > + * @pdev:	Pointer to the driver data
> > + * @seq:	Buffer to read data to
> > + */
> > +static int idt_smb_read_block(struct idt_89hpesx_dev *pdev,
> > +			      struct idt_smb_seq *seq)
> > +{
> > +	s32 sts;
> > +	u8 ccode;
> > +
> > +	/* Return error if too much data passed to send */
> > +	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
> > +		return -EINVAL;
> > +
> > +	/* Collect the command code byte */
> > +	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
> > +
> > +	/* Read block of data from the device */
> > +	sts = idt_smb_safe(read_block, pdev->client, ccode, seq->data);
> > +	if (sts != seq->bytecnt)
> > +		return (sts < SUCCESS ? sts : -ENODATA);
> > +
> > +	return SUCCESS;
> > +}
> > +
> > +/*
> > + * idt_smb_write_i2c_block() - SMBus write method when I2C_SMBUS_I2C_BLOCK_DATA
> > + *                             operation is available
> > + * @pdev:	Pointer to the driver data
> > + * @seq:	Sequence of data to be written
> > + *
> > + * NOTE It's usual SMBus write block operation, except the actual data length is
> > + * sent as first byte of data
> > + */
> > +static int idt_smb_write_i2c_block(struct idt_89hpesx_dev *pdev,
> > +				   const struct idt_smb_seq *seq)
> > +{
> > +	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
> > +
> > +	/* Return error if too much data passed to send */
> > +	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
> > +		return -EINVAL;
> > +
> > +	/* Collect the data to send. Length byte must be added prior the data */
> > +	buf[0] = seq->bytecnt;
> > +	memcpy(&buf[1], seq->data, seq->bytecnt);
> > +
> > +	/* Collect the command code byte */
> > +	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
> > +
> > +	/* Send length and block of data to the device */
> > +	return idt_smb_safe(write_i2c_block, pdev->client, ccode,
> > +		seq->bytecnt + 1, buf);
> > +}
> > +
> > +/*
> > + * idt_smb_read_i2c_block() - SMBus read method when I2C_SMBUS_I2C_BLOCK_DATA
> > + *                            operation is available
> > + * @pdev:	Pointer to the driver data
> > + * @seq:	Buffer to read data to
> > + *
> > + * NOTE It's usual SMBus read block operation, except the actual data length is
> > + * retrieved as first byte of data
> > + */
> > +static int idt_smb_read_i2c_block(struct idt_89hpesx_dev *pdev,
> > +				  struct idt_smb_seq *seq)
> > +{
> > +	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
> > +	s32 sts;
> > +
> > +	/* Return error if too much data passed to send */
> > +	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
> > +		return -EINVAL;
> > +
> > +	/* Collect the command code byte */
> > +	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
> > +
> > +	/* Read length and block of data from the device */
> > +	sts = idt_smb_safe(read_i2c_block, pdev->client, ccode,
> > +		seq->bytecnt + 1, buf);
> > +	if (sts != seq->bytecnt + 1)
> > +		return (sts < SUCCESS ? sts : -ENODATA);
> > +	if (buf[0] != seq->bytecnt)
> > +		return -ENODATA;
> > +
> > +	/* Copy retrieved data to the output data buffer */
> > +	memcpy(seq->data, &buf[1], seq->bytecnt);
> > +
> > +	return SUCCESS;
> > +}
> > +
> > +/*===========================================================================
> > + *                          EEPROM IO-operations
> > + *===========================================================================
> > + */
> > +
> > +/*
> > + * idt_eeprom_write() - EEPROM write operation
> > + * @pdev:	Pointer to the driver data
> > + * @memaddr:	Start EEPROM memory address
> > + * @len:	Length of data to be written
> > + * @data:	Data to be written to EEPROM
> > + */
> > +static int idt_eeprom_write(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
> > +			    const u8 *data)
> > +{
> > +	struct idt_eeprom_seq eeseq;
> > +	struct idt_smb_seq smbseq;
> > +	int ret, retry;
> > +	u16 idx;
> > +
> > +	/* Initialize SMBus sequence fields */
> > +	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
> > +	smbseq.data = (u8 *)&eeseq;
> > +
> > +	/* Send data byte-by-byte, checking if it is successfully written */
> > +	for (idx = 0; idx < len; idx++, memaddr++) {
> > +		/* Lock IDT SMBus device */
> > +		mutex_lock(&pdev->smb_mtx);
> > +
> > +		/* Perform write operation */
> > +		smbseq.bytecnt = EEPROM_WR_CNT;
> > +		eeseq.cmd = pdev->inieecmd | EEPROM_OP_WRITE;
> > +		eeseq.eeaddr = pdev->eeaddr;
> > +		eeseq.memaddr = cpu_to_le16(memaddr);
> > +		eeseq.data = data[idx];
> > +		ret = pdev->smb_write(pdev, &smbseq);
> > +		if (ret != SUCCESS) {
> > +			dev_err_idt(pdev,
> > +				"Failed to write 0x%04hx:0x%02hhx to eeprom",
> > +				memaddr, data[idx]);
> > +			goto err_mutex_unlock;
> > +		}
> > +
> > +		/*
> > +		 * Check whether the data is successfully written by reading
> > +		 * from the same EEPROM memory address. Sometimes EEPROM may
> > +		 * respond with NACK if it's busy with writing previous data,
> > +		 * so we need to perform a few attempts of read cycle
> > +		 */
> > +		retry = RETRY_CNT;
> > +		do {
> > +			/* Send EEPROM memory address to read data from */
> > +			smbseq.bytecnt = EEPROM_WRRD_CNT;
> > +			eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
> > +			ret = pdev->smb_write(pdev, &smbseq);
> > +			if (ret != SUCCESS) {
> > +				dev_err_idt(pdev,
> > +					"Failed to init mem address 0x%02hhx",
> > +					memaddr);
> > +				goto err_mutex_unlock;
> > +			}
> > +
> > +			/* Perform read operation */
> > +			smbseq.bytecnt = EEPROM_RD_CNT;
> > +			eeseq.data = ~data[idx];
> > +			ret = pdev->smb_read(pdev, &smbseq);
> > +			if (ret != SUCCESS) {
> > +				dev_err_idt(pdev,
> > +					"Failed to read mem address 0x%02hhx",
> > +					memaddr);
> > +				goto err_mutex_unlock;
> > +			}
> > +		} while (retry-- && (eeseq.cmd & EEPROM_NAERR));
> > +
> > +		/* Check whether IDT successfully sent data to EEPROM */
> > +		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
> > +			dev_err_idt(pdev, "Communication with EEPROM failed");
> > +			ret = -EREMOTEIO;
> > +			goto err_mutex_unlock;
> > +		}
> > +		if (eeseq.data != data[idx]) {
> > +			dev_err_idt(pdev,
> > +				"Values don't match 0x%02hhx != 0x%02hhx",
> > +				eeseq.data, data[idx]);
> > +			ret = -EREMOTEIO;
> > +			goto err_mutex_unlock;
> > +		}
> > +
> > +		/* Unlock IDT SMBus device */
> > +err_mutex_unlock:
> > +		mutex_unlock(&pdev->smb_mtx);
> > +		if (ret != SUCCESS)
> > +			return ret;
> > +	}
> > +
> > +	return SUCCESS;
> > +}
> > +
> > +/*
> > + * idt_eeprom_read() - EEPROM read operation
> > + * @pdev:	Pointer to the driver data
> > + * @memaddr:	Start EEPROM memory address
> > + * @len:	Length of data to read
> > + * @buf:	Buffer to read data to
> > + */
> > +static int idt_eeprom_read(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
> > +			   u8 *buf)
> > +{
> > +	struct idt_eeprom_seq eeseq;
> > +	struct idt_smb_seq smbseq;
> > +	u16 idx;
> > +	int ret;
> > +
> > +	/* Initialize SMBus sequence fields */
> > +	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
> > +	smbseq.data = (u8 *)&eeseq;
> > +
> > +	/* Send data one-by-one, checking if it is successfully written */
> > +	for (idx = 0; idx < len; idx++, memaddr++) {
> > +		/* Lock IDT SMBus device */
> > +		mutex_lock(&pdev->smb_mtx);
> > +
> > +		/* Send EEPROM memory address to read data from */
> > +		smbseq.bytecnt = EEPROM_WRRD_CNT;
> > +		eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
> > +		eeseq.eeaddr = pdev->eeaddr;
> > +		eeseq.memaddr = cpu_to_le16(memaddr);
> > +		ret = pdev->smb_write(pdev, &smbseq);
> > +		if (ret != SUCCESS) {
> > +			dev_err_idt(pdev, "Failed to init mem address 0x%02hhx",
> > +				memaddr);
> > +			goto err_mutex_unlock;
> > +		}
> > +
> > +		/* Perform read operation (rest of fields stay the same) */
> > +		smbseq.bytecnt = EEPROM_RD_CNT;
> > +		ret = pdev->smb_read(pdev, &smbseq);
> > +		if (ret != SUCCESS) {
> > +			dev_err_idt(pdev,
> > +				"Failed to read eeprom address 0x%02hhx",
> > +				memaddr);
> > +			ret = -EREMOTEIO;
> > +			goto err_mutex_unlock;
> > +		}
> > +
> > +		/* Check whether IDT successfully read data from EEPROM */
> > +		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
> > +			dev_err_idt(pdev, "Communication with eeprom failed");
> > +			ret = -EREMOTEIO;
> > +			goto err_mutex_unlock;
> > +		}
> > +
> > +		/* Save retrieved data */
> > +		buf[idx] = eeseq.data;
> > +
> > +		/* Unlock IDT SMBus device */
> > +err_mutex_unlock:
> > +		mutex_unlock(&pdev->smb_mtx);
> > +		if (ret != SUCCESS)
> > +			return ret;
> > +	}
> > +
> > +	return SUCCESS;
> > +}
> > +
> > +/*===========================================================================
> > + *                          CSR IO-operations
> > + *===========================================================================
> > + */
> > +
> > +/*
> > + * idt_csr_write() - CSR write operation
> > + * @pdev:	Pointer to the driver data
> > + * @csraddr:	CSR address (with no two LS bits)
> > + * @data:	Data to be written to CSR
> > + */
> > +static int idt_csr_write(struct idt_89hpesx_dev *pdev, u16 csraddr,
> > +			 const u32 data)
> > +{
> > +	struct idt_csr_seq csrseq;
> > +	struct idt_smb_seq smbseq;
> > +	int ret;
> > +
> > +	/* Initialize SMBus sequence fields */
> > +	smbseq.ccode = pdev->iniccode | CCODE_CSR;
> > +	smbseq.data = (u8 *)&csrseq;
> > +
> > +	/* Lock IDT SMBus device */
> > +	mutex_lock(&pdev->smb_mtx);
> > +
> > +	/* Perform write operation */
> > +	smbseq.bytecnt = CSR_WR_CNT;
> > +	csrseq.cmd = pdev->inicsrcmd | CSR_OP_WRITE;
> > +	csrseq.csraddr = cpu_to_le16(csraddr);
> > +	csrseq.data = cpu_to_le32(data);
> > +	ret = pdev->smb_write(pdev, &smbseq);
> > +	if (ret != SUCCESS) {
> > +		dev_err_idt(pdev, "Failed to write 0x%04x: 0x%04x to csr",
> > +			CSR_REAL_ADDR(csraddr), data);
> > +		goto err_mutex_unlock;
> > +	}
> > +
> > +	/* Send CSR address to read data from */
> > +	smbseq.bytecnt = CSR_WRRD_CNT;
> > +	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
> > +	ret = pdev->smb_write(pdev, &smbseq);
> > +	if (ret != SUCCESS) {
> > +		dev_err_idt(pdev, "Failed to init csr address 0x%04x",
> > +			CSR_REAL_ADDR(csraddr));
> > +		goto err_mutex_unlock;
> > +	}
> > +
> > +	/* Perform read operation */
> > +	smbseq.bytecnt = CSR_RD_CNT;
> > +	ret = pdev->smb_read(pdev, &smbseq);
> > +	if (ret != SUCCESS) {
> > +		dev_err_idt(pdev, "Failed to read csr 0x%04x",
> > +			CSR_REAL_ADDR(csraddr));
> > +		goto err_mutex_unlock;
> > +	}
> > +
> > +	/* Check whether IDT successfully retrieved CSR data */
> > +	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
> > +		dev_err_idt(pdev, "IDT failed to perform CSR r/w");
> > +		ret = -EREMOTEIO;
> > +		goto err_mutex_unlock;
> > +	}
> > +
> > +	/* Unlock IDT SMBus device */
> > +err_mutex_unlock:
> > +	mutex_unlock(&pdev->smb_mtx);
> > +
> > +	return ret;
> > +}
> > +
> > +/*
> > + * idt_csr_read() - CSR read operation
> > + * @pdev:	Pointer to the driver data
> > + * @csraddr:	CSR address (with no two LS bits)
> > + * @data:	Data to be written to CSR
> > + */
> > +static int idt_csr_read(struct idt_89hpesx_dev *pdev, u16 csraddr, u32 *data)
> > +{
> > +	struct idt_csr_seq csrseq;
> > +	struct idt_smb_seq smbseq;
> > +	int ret;
> > +
> > +	/* Initialize SMBus sequence fields */
> > +	smbseq.ccode = pdev->iniccode | CCODE_CSR;
> > +	smbseq.data = (u8 *)&csrseq;
> > +
> > +	/* Lock IDT SMBus device */
> > +	mutex_lock(&pdev->smb_mtx);
> > +
> > +	/* Send CSR register address before reading it */
> > +	smbseq.bytecnt = CSR_WRRD_CNT;
> > +	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
> > +	csrseq.csraddr = cpu_to_le16(csraddr);
> > +	ret = pdev->smb_write(pdev, &smbseq);
> > +	if (ret != SUCCESS) {
> > +		dev_err_idt(pdev, "Failed to init csr address 0x%04x",
> > +			CSR_REAL_ADDR(csraddr));
> > +		goto err_mutex_unlock;
> > +	}
> > +
> > +	/* Perform read operation */
> > +	smbseq.bytecnt = CSR_RD_CNT;
> > +	ret = pdev->smb_read(pdev, &smbseq);
> > +	if (ret != SUCCESS) {
> > +		dev_err_idt(pdev, "Failed to read csr 0x%04hx",
> > +			CSR_REAL_ADDR(csraddr));
> > +		goto err_mutex_unlock;
> > +	}
> > +
> > +	/* Check whether IDT successfully retrieved CSR data */
> > +	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
> > +		dev_err_idt(pdev, "IDT failed to perform CSR r/w");
> > +		ret = -EREMOTEIO;
> > +		goto err_mutex_unlock;
> > +	}
> > +
> > +	/* Save data retrieved from IDT */
> > +	*data = le32_to_cpu(csrseq.data);
> > +
> > +	/* Unlock IDT SMBus device */
> > +err_mutex_unlock:
> > +	mutex_unlock(&pdev->smb_mtx);
> > +
> > +	return ret;
> > +}
> > +
> > +/*===========================================================================
> > + *                          Sysfs-nodes IO-operations
> > + *===========================================================================
> > + */
> > +
> > +/*
> > + * idt_sysfs_eeprom_write() - EEPROM sysfs-node write callback
> > + * @filep:	Pointer to the file system node
> > + * @kobj:	Pointer to the kernel object related to the sysfs-node
> > + * @attr:	Attributes of the file
> > + * @buf:	Buffer to write data to
> > + * @off:	Offset at which data should be written to
> > + * @count:	Number of bytes to write
> > + */
> > +static ssize_t idt_sysfs_eeprom_write(struct file *filp, struct kobject *kobj,
> > +				      struct bin_attribute *attr,
> > +				      char *buf, loff_t off, size_t count)
> > +{
> > +	struct idt_89hpesx_dev *pdev;
> > +	int ret;
> > +
> > +	/* Retrieve driver data */
> > +	pdev = to_pdev_kobj(kobj);
> > +
> > +	/* Perform EEPROM write operation */
> > +	ret = idt_eeprom_write(pdev, (u16)off, (u16)count, (u8 *)buf);
> > +	return (ret != SUCCESS ? ret : count);
> > +}
> > +
> > +/*
> > + * idt_sysfs_eeprom_read() - EEPROM sysfs-node read callback
> > + * @filep:	Pointer to the file system node
> > + * @kobj:	Pointer to the kernel object related to the sysfs-node
> > + * @attr:	Attributes of the file
> > + * @buf:	Buffer to write data to
> > + * @off:	Offset at which data should be written to
> > + * @count:	Number of bytes to write
> > + */
> > +static ssize_t idt_sysfs_eeprom_read(struct file *filp, struct kobject *kobj,
> > +				     struct bin_attribute *attr,
> > +				     char *buf, loff_t off, size_t count)
> > +{
> > +	struct idt_89hpesx_dev *pdev;
> > +	int ret;
> > +
> > +	/* Retrieve driver data */
> > +	pdev = to_pdev_kobj(kobj);
> > +
> > +	/* Perform EEPROM read operation */
> > +	ret = idt_eeprom_read(pdev, (u16)off, (u16)count, (u8 *)buf);
> > +	return (ret != SUCCESS ? ret : count);
> > +}
> > +
> > +/*
> > + * idt_sysfs_csr_store() - CSR sysfs-node write callback
> > + * @kobj:	Pointer to the kernel object related to the sysfs-node
> > + * @attr:	Attributes of the file
> > + * @buf:	Buffer to write data to
> > + * @count:	Size of the buffer
> > + *
> > + * It accepts either "0x<reg addr>:0x<value>" for saving register address
> > + * and writing value to specified DWORD register or "0x<reg addr>" for
> > + * just saving register address in order to perform next read operation.
> > + *
> > + * WARNING No spaces are allowed. Incoming string must be strictly formated as:
> > + * "<reg addr>:<value>". Register address must be aligned within 4 bytes
> > + * (one DWORD).
> > + */
> > +static ssize_t idt_sysfs_csr_store(struct device *dev,
> > +				   struct device_attribute *attr,
> > +				   const char *buf, size_t count)
> > +{
> > +	struct idt_89hpesx_dev *pdev;
> > +	char *colon_ch, *csraddr_str, *csrval_str;
> > +	int ret, csraddr_len, csrval_len;
> > +	u32 csraddr, csrval;
> > +
> > +	/* Retrieve driver data */
> > +	pdev = dev_get_drvdata(dev);
> > +
> > +	/* Find position of colon in the buffer */
> > +	colon_ch = strnchr(buf, count, ':');
> > +
> > +	/*
> > +	 * If there is colon passed then new CSR value should be parsed as
> > +	 * well, so allocate buffer for CSR address substring.
> > +	 * If no colon is found, then string must have just one number with
> > +	 * no new CSR value
> > +	 */
> > +	if (colon_ch != NULL) {
> > +		csraddr_len = colon_ch - buf;
> > +		csraddr_str =
> > +			kmalloc(sizeof(char)*(csraddr_len + 1), GFP_KERNEL);
> > +		if (csraddr_str == NULL)
> > +			return -ENOMEM;
> > +		/* Copy the register address to the substring buffer */
> > +		strncpy(csraddr_str, buf, csraddr_len);
> > +		csraddr_str[csraddr_len] = '\0';
> > +		/* Register value must follow the colon */
> > +		csrval_str = colon_ch + 1;
> > +		csrval_len = strnlen(csrval_str, count - csraddr_len);
> > +	} else /* if (str_colon == NULL) */ {
> > +		csraddr_str = (char *)buf; /* Just to shut warning up */
> > +		csraddr_len = strnlen(csraddr_str, count);
> > +		csrval_str = NULL;
> > +		csrval_len = 0;
> > +	}
> > +
> > +	/* Convert CSR address to u32 value */
> > +	ret = kstrtou32(csraddr_str, 0, &csraddr);
> > +	if (ret != SUCCESS)
> > +		goto free_csraddr_str;
> > +
> > +	/* Check whether passed register address is valid */
> > +	if (csraddr > CSR_MAX || !IS_ALIGNED(csraddr, SZ_4)) {
> > +		ret = -EINVAL;
> > +		goto free_csraddr_str;
> > +	}
> > +
> > +	/* Shift register address to the right so to have u16 address */
> > +	csraddr >>= 2;
> > +
> > +	/* Parse new CSR value and send it to IDT, if colon has been found */
> > +	if (colon_ch != NULL) {
> > +		ret = kstrtou32(csrval_str, 0, &csrval);
> > +		if (ret != SUCCESS)
> > +			goto free_csraddr_str;
> > +
> > +		ret = idt_csr_write(pdev, (u16)csraddr, csrval);
> > +		if (ret != SUCCESS)
> > +			goto free_csraddr_str;
> > +	}
> > +
> > +	/* Save CSR address in the data structure for future read operations */
> > +	atomic_set(&pdev->csr, (int)csraddr);
> > +
> > +	/* Free memory only if colon has been found */
> > +free_csraddr_str:
> > +	if (colon_ch != NULL)
> > +		kfree(csraddr_str);
> > +
> > +	return (ret != SUCCESS ? ret : count);
> > +}
> > +
> > +/*
> > + * idt_sysfs_csr_show() - CSR sysfs-node read callback
> > + * @kobj:	Pointer to the kernel object related to the sysfs-node
> > + * @attr:	Attributes of the file
> > + * @buf:	Buffer to write data to
> > + *
> > + * It just prints the pair "0x<reg addr>:0x<value>" to passed buffer.
> > + */
> > +static ssize_t idt_sysfs_csr_show(struct device *dev,
> > +				  struct device_attribute *attr, char *buf)
> > +{
> > +	struct idt_89hpesx_dev *pdev;
> > +	u32 csraddr, csrval;
> > +	int ret;
> > +
> > +	/* Retrieve driver data */
> > +	pdev = dev_get_drvdata(dev);
> > +
> > +	/* Read current CSR address */
> > +	csraddr = atomic_read(&pdev->csr);
> > +
> > +	/* Perform CSR read operation */
> > +	ret = idt_csr_read(pdev, (u16)csraddr, &csrval);
> > +	if (ret != SUCCESS)
> > +		return ret;
> > +
> > +	/* Shift register address to the left so to have real address */
> > +	csraddr <<= 2;
> > +
> > +	/* Print the "0x<reg addr>:0x<value>" to buffer */
> > +	return snprintf(buf, PAGE_SIZE, "0x%05x:0x%08x\n",
> > +		(unsigned int)csraddr, (unsigned int)csrval);
> > +}
> > +
> > +/*
> > + * eeprom_attribute - EEPROM sysfs-node attributes
> > + *
> > + * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
> > + * be read-only as well if the corresponding flag is specified in OF node.
> > + */
> > +static struct bin_attribute eeprom_attribute = {
> > +	.attr = {
> > +		.name = "eeprom",
> > +		.mode = S_IRUGO | S_IWUSR
> > +	},
> > +	.size = EEPROM_DEF_SIZE,
> > +	.write = idt_sysfs_eeprom_write,
> > +	.read = idt_sysfs_eeprom_read
> > +};
> > +
> > +/*
> > + * csr_attribute - CSR sysfs-node attributes
> > + */
> > +static struct device_attribute csr_attribute = {
> > +	.attr = {
> > +		.name = "csr",
> > +		.mode = S_IRUGO | S_IWUSR
> > +	},
> > +	.store = idt_sysfs_csr_store,
> > +	.show = idt_sysfs_csr_show,
> > +};
> 
> Note, for the future, just use DEVICE_ATTR_RW() for stuff like this.
> 
> But convert it to debugfs please.
> 
> thanks,
> 
> greg k-h

Ok. I'll fix all the issues as soon as possible.

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v2 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-10-02 23:13 [PATCH] Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2016-10-30 13:53 ` Greg KH
@ 2016-11-28 22:38 ` Serge Semin
  2016-11-28 22:38   ` [PATCH v2 1/2] " Serge Semin
                     ` (2 more replies)
  1 sibling, 3 replies; 34+ messages in thread
From: Serge Semin @ 2016-11-28 22:38 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

Following changes are made in accordance with Greg KH notes as well as
fixing some found issues:
- Get rid of dev_*_idt() macros
- IDT CSR debug file is moved to debugfs
- BIN_ATTR is used to declare sysfs binary attribute
- Moved bindings file to a separate patch
- Need to create a specific bin_attribute structure for each device
- Perform a few read retries with delays if EEPROM is busy

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>

Serge Semin (2):
  MISC eeprom: Add IDT 89HPESx EEPROM/CSR driver
  MISC eeprom: Add IDT 89HPESx driver bindings file

 .../devicetree/bindings/misc/idt_89hpesx.txt       |   41 +
 drivers/misc/eeprom/Kconfig                        |   10 +
 drivers/misc/eeprom/Makefile                       |    1 +
 drivers/misc/eeprom/idt_89hpesx.c                  | 1577 ++++++++++++++++++++
 4 files changed, 1629 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

-- 
2.6.6

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v2 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-11-28 22:38 ` [PATCH v2 0/2] eeprom: " Serge Semin
@ 2016-11-28 22:38   ` Serge Semin
  2016-11-29 19:34     ` Greg KH
  2016-11-29 19:37     ` Greg KH
  2016-11-28 22:38   ` [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
  2016-11-29 22:27   ` [PATCH v3 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2 siblings, 2 replies; 34+ messages in thread
From: Serge Semin @ 2016-11-28 22:38 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

See cover-letter for changelog

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>

---
 drivers/misc/eeprom/Kconfig       |   10 +
 drivers/misc/eeprom/Makefile      |    1 +
 drivers/misc/eeprom/idt_89hpesx.c | 1577 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1588 insertions(+)
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index c4e41c2..de58762 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -100,4 +100,14 @@ config EEPROM_DIGSY_MTC_CFG
 
 	  If unsure, say N.
 
+config EEPROM_IDT_89HPESX
+	tristate "IDT 89HPESx PCIe-swtiches EEPROM / CSR support"
+	depends on I2C && SYSFS
+	help
+	  Enable this driver to get read/write access to EEPROM / CSRs
+	  over IDT PCIe-swtich i2c-slave interface.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called idt_89hpesx.
+
 endmenu
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index fc1e81d..90a5262 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o
 obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
 obj-$(CONFIG_EEPROM_93XX46)	+= eeprom_93xx46.o
 obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
+obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c
index 0000000..00cbbec
--- /dev/null
+++ b/drivers/misc/eeprom/idt_89hpesx.c
@@ -0,0 +1,1577 @@
+/*
+ *   This file is provided under a GPLv2 license.  When using or
+ *   redistributing this file, you may do so under that license.
+ *
+ *   GPL LICENSE SUMMARY
+ *
+ *   Copyright (C) 2016 T-Platforms. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or modify it
+ *   under the terms and conditions of the GNU General Public License,
+ *   version 2, as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful, but WITHOUT
+ *   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ *   more details.
+ *
+ *   You should have received a copy of the GNU General Public License along
+ *   with this program; if not, it can be found <http://www.gnu.org/licenses/>.
+ *
+ *   The full GNU General Public License is included in this distribution in
+ *   the file called "COPYING".
+ *
+ *   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.
+ *
+ * IDT PCIe-switch NTB Linux driver
+ *
+ * Contact Information:
+ * Serge Semin <fancer.lancer@gmail.com>, <Sergey.Semin@t-platforms.ru>
+ */
+/*
+ *           NOTE of the IDT 89HPESx SMBus-slave interface driver
+ *    This driver primarily is developed to have an access to EEPROM device of
+ * IDT PCIe-switches. IDT provides a simple SMBus interface to perform IO-
+ * operations from/to EEPROM, which is located at private (so called Master)
+ * SMBus of switches. Using that interface this the driver creates a simple
+ * binary sysfs-file in the device directory:
+ * /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
+ * In case if read-only flag is specified in the dts-node of device desription,
+ * User-space applications won't be able to write to the EEPROM sysfs-node.
+ *    Additionally IDT 89HPESx SMBus interface has an ability to write/read
+ * data of device CSRs. This driver exposes debugf-file to perform simple IO
+ * operations using that ability for just basic debug purpose. Particularly
+ * next file is created in the specific debugfs-directory:
+ * /sys/kernel/debug/idt_csr/
+ * Format of the debugfs-node is:
+ * $ cat /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * <CSR address>:<CSR value>
+ * So reading the content of the file gives current CSR address and it value.
+ * If User-space application wishes to change current CSR address,
+ * it can just write a proper value to the sysfs-file:
+ * $ echo "<CSR address>" > /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>
+ * If it wants to change the CSR value as well, the format of the write
+ * operation is:
+ * $ echo "<CSR address>:<CSR value>" > \
+ *        /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * CSR address and value can be any of hexadecimal, decimal or octal format.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/debugfs.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/pci_ids.h>
+#include <linux/delay.h>
+
+#define IDT_NAME		"89hpesx"
+#define IDT_89HPESX_DESC	"IDT 89HPESx SMBus-slave interface driver"
+#define IDT_89HPESX_VER		"1.0"
+
+MODULE_DESCRIPTION(IDT_89HPESX_DESC);
+MODULE_VERSION(IDT_89HPESX_VER);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("T-platforms");
+
+/*
+ * csr_dbgdir - CSR read/write operations Debugfs directory
+ */
+static struct dentry *csr_dbgdir;
+
+/*
+ * struct idt_89hpesx_dev - IDT 89HPESx device data structure
+ * @eesize:	Size of EEPROM in bytes (calculated from "idt,eecompatible")
+ * @eero:	EEPROM Read-only flag
+ * @eeaddr:	EEPROM custom address
+ *
+ * @inieecmd:	Initial cmd value for EEPROM read/write operations
+ * @inicsrcmd:	Initial cmd value for CSR read/write operations
+ * @iniccode:	Initialial command code value for IO-operations
+ *
+ * @csr:	CSR address to perform read operation
+ *
+ * @smb_write:	SMBus write method
+ * @smb_read:	SMBus read method
+ * @smb_mtx:	SMBus mutex
+ *
+ * @client:	i2c client used to perform IO operations
+ *
+ * @ee_file:	EEPROM read/write sysfs-file
+ * @csr_file:	CSR read/write debugfs-node
+ */
+struct idt_smb_seq;
+struct idt_89hpesx_dev {
+	u32 eesize;
+	bool eero;
+	u8 eeaddr;
+
+	u8 inieecmd;
+	u8 inicsrcmd;
+	u8 iniccode;
+
+	atomic_t csr;
+
+	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
+	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
+	struct mutex smb_mtx;
+
+	struct i2c_client *client;
+
+	struct bin_attribute *ee_file;
+	struct dentry *csr_dir;
+	struct dentry *csr_file;
+};
+#define to_pdev_kobj(__kobj) \
+	dev_get_drvdata(container_of(__kobj, struct device, kobj))
+
+/*
+ * struct idt_smb_seq - sequence of data to be read/written from/to IDT 89HPESx
+ * @ccode:	SMBus command code
+ * @bytecnt:	Byte count of operation
+ * @data:	Data to by written
+ */
+struct idt_smb_seq {
+	u8 ccode;
+	u8 bytecnt;
+	u8 *data;
+};
+
+/*
+ * struct idt_eeprom_seq - sequence of data to be read/written from/to EEPROM
+ * @cmd:	Transaction CMD
+ * @eeaddr:	EEPROM custom address
+ * @memaddr:	Internal memory address of EEPROM
+ * @data:	Data to be written at the memory address
+ */
+struct idt_eeprom_seq {
+	u8 cmd;
+	u8 eeaddr;
+	u16 memaddr;
+	u8 data;
+} __packed;
+
+/*
+ * struct idt_csr_seq - sequence of data to be read/written from/to CSR
+ * @cmd:	Transaction CMD
+ * @csraddr:	Internal IDT device CSR address
+ * @data:	Data to be read/written from/to the CSR address
+ */
+struct idt_csr_seq {
+	u8 cmd;
+	u16 csraddr;
+	u32 data;
+} __packed;
+
+/*
+ * SMBus command code macros
+ * @CCODE_END:		Indicates the end of transaction
+ * @CCODE_START:	Indicates the start of transaction
+ * @CCODE_CSR:		CSR read/write transaction
+ * @CCODE_EEPROM:	EEPROM read/write transaction
+ * @CCODE_BYTE:		Supplied data has BYTE length
+ * @CCODE_WORD:		Supplied data has WORD length
+ * @CCODE_BLOCK:	Supplied data has variable length passed in bytecnt
+ *			byte right following CCODE byte
+ */
+#define CCODE_END	((u8)0x01)
+#define CCODE_START	((u8)0x02)
+#define CCODE_CSR	((u8)0x00)
+#define CCODE_EEPROM	((u8)0x04)
+#define CCODE_BYTE	((u8)0x00)
+#define CCODE_WORD	((u8)0x20)
+#define CCODE_BLOCK	((u8)0x40)
+#define CCODE_PEC	((u8)0x80)
+
+/*
+ * EEPROM command macros
+ * @EEPROM_OP_WRITE:	EEPROM write operation
+ * @EEPROM_OP_READ:	EEPROM read operation
+ * @EEPROM_USA:		Use specified address of EEPROM
+ * @EEPROM_NAERR:	EEPROM device is not ready to respond
+ * @EEPROM_LAERR:	EEPROM arbitration loss error
+ * @EEPROM_MSS:		EEPROM misplace start & stop bits error
+ * @EEPROM_WR_CNT:	Bytes count to perform write operation
+ * @EEPROM_WRRD_CNT:	Bytes count to write before reading
+ * @EEPROM_RD_CNT:	Bytes count to perform read operation
+ * @EEPROM_DEF_SIZE:	Fall back size of EEPROM
+ * @EEPROM_DEF_ADDR:	Defatul EEPROM address
+ * @EEPROM_TOUT:	Timeout before retry read operation if eeprom is busy
+ */
+#define EEPROM_OP_WRITE	((u8)0x00)
+#define EEPROM_OP_READ	((u8)0x01)
+#define EEPROM_USA	((u8)0x02)
+#define EEPROM_NAERR	((u8)0x08)
+#define EEPROM_LAERR    ((u8)0x10)
+#define EEPROM_MSS	((u8)0x20)
+#define EEPROM_WR_CNT	((u8)5)
+#define EEPROM_WRRD_CNT	((u8)4)
+#define EEPROM_RD_CNT	((u8)5)
+#define EEPROM_DEF_SIZE	((u16)4096)
+#define EEPROM_DEF_ADDR	((u8)0x50)
+#define EEPROM_TOUT	(100)
+
+/*
+ * CSR command macros
+ * @CSR_DWE:		Enable all four bytes of the operation
+ * @CSR_OP_WRITE:	CSR write operation
+ * @CSR_OP_READ:	CSR read operation
+ * @CSR_RERR:		Read operation error
+ * @CSR_WERR:		Write operation error
+ * @CSR_WR_CNT:		Bytes count to perform write operation
+ * @CSR_WRRD_CNT:	Bytes count to write before reading
+ * @CSR_RD_CNT:		Bytes count to perform read operation
+ * @CSR_MAX:		Maximum CSR address
+ * @CSR_DEF:		Default CSR address
+ * @CSR_REAL_ADDR:	CSR real unshifted address
+ */
+#define CSR_DWE			((u8)0x0F)
+#define CSR_OP_WRITE		((u8)0x00)
+#define CSR_OP_READ		((u8)0x10)
+#define CSR_RERR		((u8)0x40)
+#define CSR_WERR		((u8)0x80)
+#define CSR_WR_CNT		((u8)7)
+#define CSR_WRRD_CNT		((u8)3)
+#define CSR_RD_CNT		((u8)7)
+#define CSR_MAX			((u32)0x3FFFF)
+#define CSR_DEF			((u16)0x0000)
+#define CSR_REAL_ADDR(val)	((unsigned int)val << 2)
+
+/*
+ * IDT 89HPESx basic register
+ * @IDT_VIDDID_CSR:	PCIe VID and DID of IDT 89HPESx
+ * @IDT_VID_MASK:	Mask of VID
+ */
+#define IDT_VIDDID_CSR	((u32)0x0000)
+#define IDT_VID_MASK	((u32)0xFFFF)
+
+/*
+ * IDT 89HPESx can send NACK when new command is sent before previous one
+ * fininshed execution. In this case driver retries operation
+ * certain times.
+ * @RETRY_CNT:		Number of retries before giving up and fail
+ * @idt_smb_safe:	Generate a retry loop on corresponding SMBus method
+ */
+#define RETRY_CNT (128)
+#define idt_smb_safe(ops, args...) ({ \
+	int __retry = RETRY_CNT; \
+	s32 __sts; \
+	do { \
+		__sts = i2c_smbus_ ## ops ## _data(args); \
+	} while (__retry-- && __sts < 0); \
+	__sts; \
+})
+
+/*===========================================================================
+ *                         i2c bus level IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_smb_write_byte() - SMBus write method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_byte(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied data sending byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Send data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_byte() - SMBus read method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_byte(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied buffer receiving byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Read data from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_word() - SMBus write method when I2C_SMBUS_BYTE_DATA and
+ *                        I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_word(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data sending two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Send word data to the device */
+		sts = idt_smb_safe(write_word, pdev->client, ccode,
+			*(u16 *)&seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	/* If there is odd number of bytes then send just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Send byte data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_word() - SMBus read method when I2C_SMBUS_BYTE_DATA and
+ *                       I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_word(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data reading two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Read word data from the device */
+		sts = idt_smb_safe(read_word, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		*(u16 *)&seq->data[idx] = (u16)sts;
+	}
+
+	/* If there is odd number of bytes then receive just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Read last data byte from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_block() - SMBus write method when I2C_SMBUS_BLOCK_DATA
+ *                         operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_block(struct idt_89hpesx_dev *pdev,
+			       const struct idt_smb_seq *seq)
+{
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send block of data to the device */
+	return idt_smb_safe(write_block, pdev->client, ccode, seq->bytecnt,
+		seq->data);
+}
+
+/*
+ * idt_smb_read_block() - SMBus read method when I2C_SMBUS_BLOCK_DATA
+ *                        operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_block(struct idt_89hpesx_dev *pdev,
+			      struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read block of data from the device */
+	sts = idt_smb_safe(read_block, pdev->client, ccode, seq->data);
+	if (sts != seq->bytecnt)
+		return (sts < 0 ? sts : -ENODATA);
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_i2c_block() - SMBus write method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                             operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ *
+ * NOTE It's usual SMBus write block operation, except the actual data length is
+ * sent as first byte of data
+ */
+static int idt_smb_write_i2c_block(struct idt_89hpesx_dev *pdev,
+				   const struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the data to send. Length byte must be added prior the data */
+	buf[0] = seq->bytecnt;
+	memcpy(&buf[1], seq->data, seq->bytecnt);
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send length and block of data to the device */
+	return idt_smb_safe(write_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+}
+
+/*
+ * idt_smb_read_i2c_block() - SMBus read method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                            operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ *
+ * NOTE It's usual SMBus read block operation, except the actual data length is
+ * retrieved as first byte of data
+ */
+static int idt_smb_read_i2c_block(struct idt_89hpesx_dev *pdev,
+				  struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+	s32 sts;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read length and block of data from the device */
+	sts = idt_smb_safe(read_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+	if (sts != seq->bytecnt + 1)
+		return (sts < 0 ? sts : -ENODATA);
+	if (buf[0] != seq->bytecnt)
+		return -ENODATA;
+
+	/* Copy retrieved data to the output data buffer */
+	memcpy(seq->data, &buf[1], seq->bytecnt);
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          EEPROM IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_eeprom_read_byte() - read just one byte from EEPROM
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_read_byte(struct idt_89hpesx_dev *pdev, u16 memaddr,
+				u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret, retry;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/*
+	 * Sometimes EEPROM may respond with NACK if it's busy with previous
+	 * operation, so we need to perform a few attempts of read cycle
+	 */
+	retry = RETRY_CNT;
+	do {
+		/* Send EEPROM memory address to read data from */
+		smbseq.bytecnt = EEPROM_WRRD_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to init eeprom addr 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Perform read operation */
+		smbseq.bytecnt = EEPROM_RD_CNT;
+		ret = pdev->smb_read(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to read eeprom data 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Restart read operation if the device is busy */
+		if (retry && (eeseq.cmd & EEPROM_NAERR)) {
+			dev_dbg(dev, "EEPROM busy, retry reading after %d ms",
+				EEPROM_TOUT);
+			msleep(EEPROM_TOUT);
+			continue;
+		}
+
+		/* Check whether IDT successfully read data from EEPROM */
+		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
+			dev_err(dev,
+				"Communication with eeprom failed, cmd 0x%hhx",
+				eeseq.cmd);
+			ret = -EREMOTEIO;
+			break;
+		}
+
+		/* Save retrieved data and exit the loop */
+		*data = eeseq.data;
+		break;
+	} while (retry--);
+
+	/* Return the status of operation */
+	return ret;
+}
+
+/*
+ * idt_eeprom_write() - EEPROM write operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to be written
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_write(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			    const u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+	u16 idx;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/* Send data byte-by-byte, checking if it is successfully written */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Perform write operation */
+		smbseq.bytecnt = EEPROM_WR_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_WRITE;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		eeseq.data = data[idx];
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev,
+				"Failed to write 0x%04hx:0x%02hhx to eeprom",
+				memaddr, data[idx]);
+			goto err_mutex_unlock;
+		}
+
+		/*
+		 * Check whether the data is successfully written by reading
+		 * from the same EEPROM memory address.
+		 */
+		eeseq.data = ~data[idx];
+		ret = idt_eeprom_read_byte(pdev, memaddr, &eeseq.data);
+		if (ret != 0)
+			goto err_mutex_unlock;
+
+		/* Check whether the read byte is the same as written one */
+		if (eeseq.data != data[idx]) {
+			dev_err(dev, "Values don't match 0x%02hhx != 0x%02hhx",
+				eeseq.data, data[idx]);
+			ret = -EREMOTEIO;
+			goto err_mutex_unlock;
+		}
+
+		/* Unlock IDT SMBus device */
+err_mutex_unlock:
+		mutex_unlock(&pdev->smb_mtx);
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_eeprom_read() - EEPROM read operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to read
+ * @buf:	Buffer to read data to
+ */
+static int idt_eeprom_read(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			   u8 *buf)
+{
+	int ret;
+	u16 idx;
+
+	/* Read data byte-by-byte, retrying if it wasn't successful */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Just read the byte to the buffer */
+		ret = idt_eeprom_read_byte(pdev, memaddr, &buf[idx]);
+
+		/* Unlock IDT SMBus device */
+		mutex_unlock(&pdev->smb_mtx);
+
+		/* Return error if read operation failed */
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          CSR IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_csr_write() - CSR write operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_write(struct idt_89hpesx_dev *pdev, u16 csraddr,
+			 const u32 data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Perform write operation */
+	smbseq.bytecnt = CSR_WR_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_WRITE;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	csrseq.data = cpu_to_le32(data);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write 0x%04x: 0x%04x to csr",
+			CSR_REAL_ADDR(csraddr), data);
+		goto err_mutex_unlock;
+	}
+
+	/* Send CSR address to read data from */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*
+ * idt_csr_read() - CSR read operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_read(struct idt_89hpesx_dev *pdev, u16 csraddr, u32 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Send CSR register address before reading it */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04hx",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Save data retrieved from IDT */
+	*data = le32_to_cpu(csrseq.data);
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*===========================================================================
+ *                          Sysfs-nodes IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_sysfs_eeprom_write() - EEPROM sysfs-node write callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t idt_sysfs_eeprom_write(struct file *filp, struct kobject *kobj,
+				      struct bin_attribute *attr,
+				      char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = to_pdev_kobj(kobj);
+
+	/* Perform EEPROM write operation */
+	ret = idt_eeprom_write(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_sysfs_eeprom_read() - EEPROM sysfs-node read callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t idt_sysfs_eeprom_read(struct file *filp, struct kobject *kobj,
+				     struct bin_attribute *attr,
+				     char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = to_pdev_kobj(kobj);
+
+	/* Perform EEPROM read operation */
+	ret = idt_eeprom_read(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_write() - CSR debugfs-node write callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to read data from
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It accepts either "0x<reg addr>:0x<value>" for saving register address
+ * and writing value to specified DWORD register or "0x<reg addr>" for
+ * just saving register address in order to perform next read operation.
+ *
+ * WARNING No spaces are allowed. Incoming string must be strictly formated as:
+ * "<reg addr>:<value>". Register address must be aligned within 4 bytes
+ * (one DWORD).
+ */
+static ssize_t idt_dbgfs_csr_write(struct file *filep, const char __user *ubuf,
+				   size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	char *colon_ch, *csraddr_str, *csrval_str;
+	int ret, csraddr_len, csrval_len;
+	u32 csraddr, csrval;
+	char *buf;
+
+	/* Copy data from User-space */
+	buf = kmalloc(count + 1, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = simple_write_to_buffer(buf, count, offp, ubuf, count);
+	if (ret < 0)
+		goto free_buf;
+	buf[count] = 0;
+
+	/* Find position of colon in the buffer */
+	colon_ch = strnchr(buf, count, ':');
+
+	/*
+	 * If there is colon passed then new CSR value should be parsed as
+	 * well, so allocate buffer for CSR address substring.
+	 * If no colon is found, then string must have just one number with
+	 * no new CSR value
+	 */
+	if (colon_ch != NULL) {
+		csraddr_len = colon_ch - buf;
+		csraddr_str =
+			kmalloc(sizeof(char)*(csraddr_len + 1), GFP_KERNEL);
+		if (csraddr_str == NULL)
+			return -ENOMEM;
+		/* Copy the register address to the substring buffer */
+		strncpy(csraddr_str, buf, csraddr_len);
+		csraddr_str[csraddr_len] = '\0';
+		/* Register value must follow the colon */
+		csrval_str = colon_ch + 1;
+		csrval_len = strnlen(csrval_str, count - csraddr_len);
+	} else /* if (str_colon == NULL) */ {
+		csraddr_str = (char *)buf; /* Just to shut warning up */
+		csraddr_len = strnlen(csraddr_str, count);
+		csrval_str = NULL;
+		csrval_len = 0;
+	}
+
+	/* Convert CSR address to u32 value */
+	ret = kstrtou32(csraddr_str, 0, &csraddr);
+	if (ret != 0)
+		goto free_csraddr_str;
+
+	/* Check whether passed register address is valid */
+	if (csraddr > CSR_MAX || !IS_ALIGNED(csraddr, SZ_4)) {
+		ret = -EINVAL;
+		goto free_csraddr_str;
+	}
+
+	/* Shift register address to the right so to have u16 address */
+	csraddr >>= 2;
+
+	/* Parse new CSR value and send it to IDT, if colon has been found */
+	if (colon_ch != NULL) {
+		ret = kstrtou32(csrval_str, 0, &csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+
+		ret = idt_csr_write(pdev, (u16)csraddr, csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+	}
+
+	/* Save CSR address in the data structure for future read operations */
+	atomic_set(&pdev->csr, (int)csraddr);
+
+	/* Free memory only if colon has been found */
+free_csraddr_str:
+	if (colon_ch != NULL)
+		kfree(csraddr_str);
+
+	/* Free buffer allocated for data retrieved from User-space */
+free_buf:
+	kfree(buf);
+
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_read() - CSR debugfs-node read callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to write data to
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It just prints the pair "0x<reg addr>:0x<value>" to passed buffer.
+ */
+#define CSRBUF_SIZE	((size_t)32)
+static ssize_t idt_dbgfs_csr_read(struct file *filep, char __user *ubuf,
+				  size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	u32 csraddr, csrval;
+	char buf[CSRBUF_SIZE];
+	int ret, size;
+
+	/* Read current CSR address */
+	csraddr = atomic_read(&pdev->csr);
+
+	/* Perform CSR read operation */
+	ret = idt_csr_read(pdev, (u16)csraddr, &csrval);
+	if (ret != 0)
+		return ret;
+
+	/* Shift register address to the left so to have real address */
+	csraddr <<= 2;
+
+	/* Print the "0x<reg addr>:0x<value>" to buffer */
+	size = snprintf(buf, CSRBUF_SIZE, "0x%05x:0x%08x\n",
+		(unsigned int)csraddr, (unsigned int)csrval);
+
+	/* Copy data to User-space */
+	return simple_read_from_buffer(ubuf, count, offp, buf, size);
+}
+
+/*
+ * eeprom_attribute - EEPROM sysfs-node attributes
+ *
+ * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
+ * be read-only as well if the corresponding flag is specified in OF node.
+ */
+BIN_ATTR(eeprom, 0644, idt_sysfs_eeprom_read, idt_sysfs_eeprom_write,
+	 EEPROM_DEF_SIZE);
+
+/*
+ * csr_dbgfs_ops - CSR debugfs-node read/write operations
+ */
+static const struct file_operations csr_dbgfs_ops = {
+	.owner = THIS_MODULE,
+	.open = simple_open,
+	.write = idt_dbgfs_csr_write,
+	.read = idt_dbgfs_csr_read
+};
+
+/*===========================================================================
+ *                       Driver init/deinit methods
+ *===========================================================================
+ */
+
+/*
+ * idt_set_defval() - set default device data parameters
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_set_defval(struct idt_89hpesx_dev *pdev)
+{
+	/* If OF info is missing then use next values */
+	pdev->eesize = EEPROM_DEF_SIZE;
+	pdev->eero = true;
+	pdev->inieecmd = 0;
+	pdev->eeaddr = EEPROM_DEF_ADDR << 1;
+}
+
+#ifdef CONFIG_OF
+/*
+ * idt_get_ofdata() - get IDT i2c-device parameters from device tree
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	struct device_node *node = pdev->client->dev.of_node;
+	struct device *dev = &pdev->client->dev;
+	const __be32 *val;
+
+	/* Read dts node parameters */
+	if (node) {
+		/* Get EEPROM size from 'idt,eesize' */
+		val = of_get_property(node, "idt,eesize", NULL);
+		if (val != NULL) {
+			pdev->eesize = be32_to_cpup(val);
+			if (!is_power_of_2(pdev->eesize))
+				dev_warn(dev,
+					"EEPROM size %u is not power of 2",
+					pdev->eesize);
+		} else /* if (val == NULL) */ {
+			pdev->eesize = EEPROM_DEF_SIZE;
+			dev_warn(dev, "No EEPROM size, set default %u bytes",
+				pdev->eesize);
+		}
+
+		/* Get custom EEPROM address from 'idt,eeaddr' */
+		val = of_get_property(node, "idt,eeaddr", NULL);
+		if (val != NULL) {
+			pdev->inieecmd = EEPROM_USA;
+			pdev->eeaddr = be32_to_cpup(val) << 1;
+		} else /* if (val != NULL) */ {
+			pdev->inieecmd = 0;
+			pdev->eeaddr = EEPROM_DEF_ADDR << 1;
+		}
+
+		/* Check EEPROM 'read-only' flag */
+		if (of_get_property(node, "read-only", NULL))
+			pdev->eero = true;
+		else /* if (!of_get_property(node, "read-only", NULL)) */
+			pdev->eero = false;
+	} else {
+		dev_warn(dev, "No dts node, set default values");
+		idt_set_defval(pdev);
+	}
+}
+#else
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	dev_warn(dev, "OF table is unsupported, set default values");
+
+	/* Nothing we can do, just set the default values */
+	idt_set_defval(pdev);
+}
+#endif /* CONFIG_OF */
+
+/*
+ * idt_create_pdev() - create and init data structure of the driver
+ * @client:	i2c client of IDT PCIe-switch device
+ */
+static struct idt_89hpesx_dev *idt_create_pdev(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev;
+
+	/* Allocate memory for driver data */
+	pdev = devm_kmalloc(&client->dev, sizeof(struct idt_89hpesx_dev),
+		GFP_KERNEL);
+	if (pdev == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	/* Initialize basic fields of the data */
+	pdev->client = client;
+	i2c_set_clientdata(client, pdev);
+
+	/* Read OF nodes information */
+	idt_get_ofdata(pdev);
+
+	/* Initialize basic CSR CMD field - use full DWORD-sized r/w ops */
+	pdev->inicsrcmd = CSR_DWE;
+	atomic_set(&pdev->csr, CSR_DEF);
+
+	/* Enable Packet Error Checking if it's supported by adapter */
+	if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) {
+		pdev->iniccode = CCODE_PEC;
+		client->flags |= I2C_CLIENT_PEC;
+	} else /* PEC is unsupported */ {
+		pdev->iniccode = 0;
+	}
+
+	dev_dbg(&client->dev, "IDT 89HPESx data created");
+
+	return pdev;
+}
+
+/*
+ * idt_free_pdev() - free data structure of the driver
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_free_pdev(struct idt_89hpesx_dev *pdev)
+{
+	/* Clear driver data from device private field */
+	i2c_set_clientdata(pdev->client, NULL);
+
+	/* Just free memory allocated for data */
+	devm_kfree(&pdev->client->dev, pdev);
+
+	dev_dbg(&pdev->client->dev, "IDT 89HPESx data discarded");
+}
+
+/*
+ * idt_set_smbus_ops() - set supported SMBus operations
+ * @pdev:	Pointer to the driver data
+ * Return status of smbus check operations
+ */
+static int idt_set_smbus_ops(struct idt_89hpesx_dev *pdev)
+{
+	struct i2c_adapter *adapter = pdev->client->adapter;
+	struct device *dev = &pdev->client->dev;
+
+	/* Check i2c adapter read functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_READ_BLOCK_DATA)) {
+		pdev->smb_read = idt_smb_read_block;
+		dev_dbg(dev, "SMBus block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+		pdev->smb_read = idt_smb_read_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_word;
+		dev_warn(dev, "Use slow word/byte SMBus read ops");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_byte;
+		dev_warn(dev, "Use slow byte SMBus read op");
+	} else /* no supported smbus read operations */ {
+		dev_err(dev, "No supported SMBus read op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Check i2c adapter write functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) {
+		pdev->smb_write = idt_smb_write_block;
+		dev_dbg(dev, "SMBus block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
+		pdev->smb_write = idt_smb_write_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_word;
+		dev_warn(dev, "Use slow word/byte SMBus write op");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_byte;
+		dev_warn(dev, "Use slow byte SMBus write op");
+	} else /* no supported smbus write operations */ {
+		dev_err(dev, "No supported SMBus write op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Initialize IDT SMBus slave interface mutex */
+	mutex_init(&pdev->smb_mtx);
+
+	dev_dbg(dev, "SMBus functionality successfully checked");
+
+	return 0;
+}
+
+/*
+ * idt_check_dev() - check whether it's really IDT 89HPESx device
+ * @pdev:	Pointer to the driver data
+ * Return status of i2c adapter check operation
+ */
+static int idt_check_dev(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	u32 viddid;
+	int ret;
+
+	/* Read VID and DID directly from IDT memory space */
+	ret = idt_csr_read(pdev, IDT_VIDDID_CSR, &viddid);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read VID/DID");
+		return ret;
+	}
+
+	/* Check whether it's IDT device */
+	if ((viddid & IDT_VID_MASK) != PCI_VENDOR_ID_IDT) {
+		dev_err(dev, "Got unsupported VID/DID: 0x%08x", viddid);
+		return -ENODEV;
+	}
+
+	dev_info(dev, "Found IDT 89HPES device VID:0x%04x, DID:0x%04x",
+		(viddid & IDT_VID_MASK), (viddid >> 16));
+
+	return 0;
+}
+
+/*
+ * idt_create_sysfs_files() - create sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+static int idt_create_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	int ret;
+
+	/* Allocate memory for attribute file */
+	pdev->ee_file = devm_kmalloc(dev, sizeof(*pdev->ee_file), GFP_KERNEL);
+	if (!pdev->ee_file)
+		return -ENOMEM;
+
+	/* Copy the declared EEPROM attr structure to change some of fields */
+	memcpy(pdev->ee_file, &bin_attr_eeprom, sizeof(*pdev->ee_file));
+
+	/* In case of read-only EEPROM get rid of write ability */
+	if (pdev->eero) {
+		pdev->ee_file->attr.mode &= ~0200;
+		pdev->ee_file->write = NULL;
+	}
+	/* Create EEPROM sysfs file */
+	pdev->ee_file->size = pdev->eesize;
+	ret = sysfs_create_bin_file(&dev->kobj, pdev->ee_file);
+	if (ret != 0) {
+		kfree(pdev->ee_file);
+		dev_err(dev, "Failed to create EEPROM sysfs-node");
+		return ret;
+	}
+
+	dev_dbg(dev, "Sysfs-files created");
+
+	return 0;
+}
+
+/*
+ * idt_remove_sysfs_files() - remove sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	/* Remove EEPROM sysfs file */
+	sysfs_remove_bin_file(&dev->kobj, pdev->ee_file);
+
+	/* Free memory allocated for bin_attribute structure */
+	kfree(pdev->ee_file);
+
+	dev_dbg(dev, "Sysfs-files removed");
+}
+
+/*
+ * idt_create_dbgfs_files() - create debugfs files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+#define CSRNAME_LEN	((size_t)32)
+static int idt_create_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	struct i2c_client *cli = pdev->client;
+	char fname[CSRNAME_LEN];
+
+	/* Initialize basic value of CSR debugfs dentries */
+	pdev->csr_dir = NULL;
+	pdev->csr_file = NULL;
+
+	/* Return failure if root directory doesn't exist */
+	if (!csr_dbgdir) {
+		dev_dbg(dev, "No Debugfs root directory");
+		return -EINVAL;
+	}
+
+	/* Create Debugfs directory for CSR file */
+	snprintf(fname, CSRNAME_LEN, "%d-%04hx", cli->adapter->nr, cli->addr);
+	pdev->csr_dir = debugfs_create_dir(fname, csr_dbgdir);
+	if (IS_ERR_OR_NULL(pdev->csr_dir)) {
+		dev_err(dev, "Failed to create CSR node directory");
+		return -EINVAL;
+	}
+
+	/* Create Debugfs file for CSR read/write operations */
+	pdev->csr_file = debugfs_create_file(cli->name, 0600,
+		pdev->csr_dir, pdev, &csr_dbgfs_ops);
+	if (IS_ERR_OR_NULL(pdev->csr_file)) {
+		dev_err(dev, "Failed to create CSR dbgfs-node");
+		debugfs_remove_recursive(pdev->csr_dir);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "Debugfs-files created");
+
+	return 0;
+}
+
+/*
+ * idt_remove_dbgfs_files() - remove debugfs files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	/* Remove CSR directory and it sysfs-node */
+	debugfs_remove_recursive(pdev->csr_dir);
+
+	dev_dbg(&pdev->client->dev, "Debugfs-files removed");
+}
+
+/*
+ * idt_probe() - IDT 89HPESx driver probe() callback method
+ */
+static int idt_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Create driver data */
+	pdev = idt_create_pdev(client);
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	/* Set SMBus operations */
+	ret = idt_set_smbus_ops(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Check whether it is truly IDT 89HPESx device */
+	ret = idt_check_dev(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create sysfs files */
+	ret = idt_create_sysfs_files(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create debugfs files */
+	(void)idt_create_dbgfs_files(pdev);
+
+	dev_dbg(&client->dev, "IDT %s device probed", id->name);
+
+	return 0;
+
+err_free_pdev:
+	idt_free_pdev(pdev);
+
+	return ret;
+}
+
+/*
+ * idt_remove() - IDT 89HPESx driver remove() callback method
+ */
+static int idt_remove(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev = i2c_get_clientdata(client);
+
+	/* Remove debugfs files first */
+	idt_remove_dbgfs_files(pdev);
+
+	/* Remove sysfs files */
+	idt_remove_sysfs_files(pdev);
+
+	/* Discard driver data structure */
+	idt_free_pdev(pdev);
+
+	dev_dbg(&client->dev, "IDT 89HPESx device removed");
+
+	return 0;
+}
+
+/*
+ * idt_ids - supported IDT 89HPESx devices
+ */
+static const struct i2c_device_id idt_ids[] = {
+	{ "89hpes8nt2", 0 },
+	{ "89hpes12nt3", 0 },
+
+	{ "89hpes24nt6ag2", 0 },
+	{ "89hpes32nt8ag2", 0 },
+	{ "89hpes32nt8bg2", 0 },
+	{ "89hpes12nt12g2", 0 },
+	{ "89hpes16nt16g2", 0 },
+	{ "89hpes24nt24g2", 0 },
+	{ "89hpes32nt24ag2", 0 },
+	{ "89hpes32nt24bg2", 0 },
+
+	{ "89hpes12n3", 0 },
+	{ "89hpes12n3a", 0 },
+	{ "89hpes24n3", 0 },
+	{ "89hpes24n3a", 0 },
+
+	{ "89hpes32h8", 0 },
+	{ "89hpes32h8g2", 0 },
+	{ "89hpes48h12", 0 },
+	{ "89hpes48h12g2", 0 },
+	{ "89hpes48h12ag2", 0 },
+	{ "89hpes16h16", 0 },
+	{ "89hpes22h16", 0 },
+	{ "89hpes22h16g2", 0 },
+	{ "89hpes34h16", 0 },
+	{ "89hpes34h16g2", 0 },
+	{ "89hpes64h16", 0 },
+	{ "89hpes64h16g2", 0 },
+	{ "89hpes64h16ag2", 0 },
+
+	/* { "89hpes3t3", 0 }, // No SMBus-slave iface */
+	{ "89hpes12t3g2", 0 },
+	{ "89hpes24t3g2", 0 },
+	/* { "89hpes4t4", 0 }, // No SMBus-slave iface */
+	{ "89hpes16t4", 0 },
+	{ "89hpes4t4g2", 0 },
+	{ "89hpes10t4g2", 0 },
+	{ "89hpes16t4g2", 0 },
+	{ "89hpes16t4ag2", 0 },
+	{ "89hpes5t5", 0 },
+	{ "89hpes6t5", 0 },
+	{ "89hpes8t5", 0 },
+	{ "89hpes8t5a", 0 },
+	{ "89hpes24t6", 0 },
+	{ "89hpes6t6g2", 0 },
+	{ "89hpes24t6g2", 0 },
+	{ "89hpes16t7", 0 },
+	{ "89hpes32t8", 0 },
+	{ "89hpes32t8g2", 0 },
+	{ "89hpes48t12", 0 },
+	{ "89hpes48t12g2", 0 },
+	{ /* END OF LIST */ }
+};
+MODULE_DEVICE_TABLE(i2c, idt_ids);
+
+/*
+ * idt_driver - IDT 89HPESx driver structure
+ */
+static struct i2c_driver idt_driver = {
+	.driver = {
+		.name = IDT_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = idt_probe,
+	.remove = idt_remove,
+	.id_table = idt_ids,
+};
+
+/*
+ * idt_init() - IDT 89HPESx driver init() callback method
+ */
+static int __init idt_init(void)
+{
+	/* Create Debugfs directory first */
+	if (debugfs_initialized())
+		csr_dbgdir = debugfs_create_dir("idt_csr", NULL);
+
+	/* Add new i2c-device driver */
+	return i2c_add_driver(&idt_driver);
+}
+module_init(idt_init);
+
+/*
+ * idt_exit() - IDT 89HPESx driver exit() callback method
+ */
+static void __exit idt_exit(void)
+{
+	/* Discard debugfs directory and all files if any */
+	debugfs_remove_recursive(csr_dbgdir);
+
+	/* Unregister i2c-device driver */
+	i2c_del_driver(&idt_driver);
+}
+module_exit(idt_exit);
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-11-28 22:38 ` [PATCH v2 0/2] eeprom: " Serge Semin
  2016-11-28 22:38   ` [PATCH v2 1/2] " Serge Semin
@ 2016-11-28 22:38   ` Serge Semin
  2016-11-29 19:34     ` Greg KH
  2016-12-05 14:46     ` Rob Herring
  2016-11-29 22:27   ` [PATCH v3 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2 siblings, 2 replies; 34+ messages in thread
From: Serge Semin @ 2016-11-28 22:38 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

See cover-letter for changelog

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>

---
 .../devicetree/bindings/misc/idt_89hpesx.txt       | 41 ++++++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt

diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
index 0000000..469cc93
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
@@ -0,0 +1,41 @@
+EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
+
+Required properties:
+  - compatible : should be "<manufacturer>,<type>"
+		 Basically there is only one manufacturer: idt, but some
+		 compatible devices may be produced in future. Following devices
+		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
+		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
+		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
+		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
+		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
+		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
+		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
+		 89hpes64h16ag2;
+		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
+		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
+		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
+		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
+		 89hpes48t12, 89hpes48t12g2.
+		 Current implementation of the driver doesn't have any device-
+		 specific functionalities. But since each of them differs
+		 by registers mapping, CSRs read/write restrictions can be
+		 added in future.
+  - reg :	 I2C address of the IDT 89HPES device.
+
+Optional properties:
+  - read-only :	 Parameterless property disables writes to the EEPROM
+  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
+		 (default value is 4096 bytes if option isn't specified)
+  - idt,eeaddr : Custom address of EEPROM device
+		 (If not specified IDT 89HPESx device will try to communicate
+		  with EEPROM sited by default address - 0x50)
+
+Example:
+	idt_pcie_sw@60 {
+		compatible = "idt,89hpes12nt3";
+		reg = <0x60>;
+		read-only;
+		idt,eesize = <65536>;
+		idt,eeaddr = <0x50>;
+	};
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-11-28 22:38   ` [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
@ 2016-11-29 19:34     ` Greg KH
  2016-11-29 21:15       ` Serge Semin
  2016-12-05 14:46     ` Rob Herring
  1 sibling, 1 reply; 34+ messages in thread
From: Greg KH @ 2016-11-29 19:34 UTC (permalink / raw)
  To: Serge Semin
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Tue, Nov 29, 2016 at 01:38:21AM +0300, Serge Semin wrote:
> See cover-letter for changelog

There is no cover letter in an individual patch when it gets committed
to the tree...

So please fix, personally, I never read cover letters, each patch should
be "obvious" on it's own :)

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-11-28 22:38   ` [PATCH v2 1/2] " Serge Semin
@ 2016-11-29 19:34     ` Greg KH
  2016-11-29 19:37     ` Greg KH
  1 sibling, 0 replies; 34+ messages in thread
From: Greg KH @ 2016-11-29 19:34 UTC (permalink / raw)
  To: Serge Semin
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Tue, Nov 29, 2016 at 01:38:20AM +0300, Serge Semin wrote:
> See cover-letter for changelog

Same here.

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-11-28 22:38   ` [PATCH v2 1/2] " Serge Semin
  2016-11-29 19:34     ` Greg KH
@ 2016-11-29 19:37     ` Greg KH
  2016-11-29 21:16       ` Serge Semin
  1 sibling, 1 reply; 34+ messages in thread
From: Greg KH @ 2016-11-29 19:37 UTC (permalink / raw)
  To: Serge Semin
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Tue, Nov 29, 2016 at 01:38:20AM +0300, Serge Semin wrote:
> +struct idt_89hpesx_dev {
> +	u32 eesize;
> +	bool eero;
> +	u8 eeaddr;
> +
> +	u8 inieecmd;
> +	u8 inicsrcmd;
> +	u8 iniccode;
> +
> +	atomic_t csr;
> +
> +	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
> +	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
> +	struct mutex smb_mtx;
> +
> +	struct i2c_client *client;
> +
> +	struct bin_attribute *ee_file;
> +	struct dentry *csr_dir;
> +	struct dentry *csr_file;
> +};
> +#define to_pdev_kobj(__kobj) \
> +	dev_get_drvdata(container_of(__kobj, struct device, kobj))

Is it a struct device, or a kobject?  This is totally confusing to me.

And can't you just use kobj_to_dev()?

> +/*
> + * eeprom_attribute - EEPROM sysfs-node attributes
> + *
> + * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
> + * be read-only as well if the corresponding flag is specified in OF node.
> + */
> +BIN_ATTR(eeprom, 0644, idt_sysfs_eeprom_read, idt_sysfs_eeprom_write,
> +	 EEPROM_DEF_SIZE);

static?

And BIN_ATTR_RW()?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-11-29 19:34     ` Greg KH
@ 2016-11-29 21:15       ` Serge Semin
  0 siblings, 0 replies; 34+ messages in thread
From: Serge Semin @ 2016-11-29 21:15 UTC (permalink / raw)
  To: Greg KH
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Tue, Nov 29, 2016 at 08:34:36PM +0100, Greg KH <gregkh@linuxfoundation.org> wrote:
> On Tue, Nov 29, 2016 at 01:38:21AM +0300, Serge Semin wrote:
> > See cover-letter for changelog
> 
> There is no cover letter in an individual patch when it gets committed
> to the tree...
> 
> So please fix, personally, I never read cover letters, each patch should
> be "obvious" on it's own :)
> 
> thanks,
> 
> greg k-h

Understood. I'll send the v3 of altered patchset over with individual messages
for each patch.

Thanks,
-Sergey

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-11-29 19:37     ` Greg KH
@ 2016-11-29 21:16       ` Serge Semin
  2016-11-29 21:24         ` Greg KH
  0 siblings, 1 reply; 34+ messages in thread
From: Serge Semin @ 2016-11-29 21:16 UTC (permalink / raw)
  To: Greg KH
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Tue, Nov 29, 2016 at 08:37:50PM +0100, Greg KH <gregkh@linuxfoundation.org> wrote:
> On Tue, Nov 29, 2016 at 01:38:20AM +0300, Serge Semin wrote:
> > +struct idt_89hpesx_dev {
> > +	u32 eesize;
> > +	bool eero;
> > +	u8 eeaddr;
> > +
> > +	u8 inieecmd;
> > +	u8 inicsrcmd;
> > +	u8 iniccode;
> > +
> > +	atomic_t csr;
> > +
> > +	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
> > +	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
> > +	struct mutex smb_mtx;
> > +
> > +	struct i2c_client *client;
> > +
> > +	struct bin_attribute *ee_file;
> > +	struct dentry *csr_dir;
> > +	struct dentry *csr_file;
> > +};
> > +#define to_pdev_kobj(__kobj) \
> > +	dev_get_drvdata(container_of(__kobj, struct device, kobj))
> 
> Is it a struct device, or a kobject?  This is totally confusing to me.
> 
> And can't you just use kobj_to_dev()?
> 

I just didn't know about kobj_to_dev() inline function. Totally agree that
container_of() should be replaced with it.
What does look confusing to you? Do you mean the name "to_pdev_kobj" of the
macro?

> > +/*
> > + * eeprom_attribute - EEPROM sysfs-node attributes
> > + *
> > + * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
> > + * be read-only as well if the corresponding flag is specified in OF node.
> > + */
> > +BIN_ATTR(eeprom, 0644, idt_sysfs_eeprom_read, idt_sysfs_eeprom_write,
> > +	 EEPROM_DEF_SIZE);
> 
> static?
> 
> And BIN_ATTR_RW()?
> 
> thanks,
> 
> greg k-h

Of course it should be static. Thanks for noticing that.
But I intentionally utilized BIN_ATTR() instead of BIN_ATTR_RW(), because
the last one implies to define the read/write methods with names
"_name##_read"/"_name##_write", which totally get out of naming within the
driver source code. To tell the truth macro BIN_ATTR_RW() isn't that
popular in the kernel. Neither is BIN_ATTR() macro, but it suites my driver
better than the another one.

Thanks,
-Sergey

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-11-29 21:16       ` Serge Semin
@ 2016-11-29 21:24         ` Greg KH
  2016-11-29 21:45           ` Serge Semin
  0 siblings, 1 reply; 34+ messages in thread
From: Greg KH @ 2016-11-29 21:24 UTC (permalink / raw)
  To: Serge Semin
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Wed, Nov 30, 2016 at 12:16:25AM +0300, Serge Semin wrote:
> On Tue, Nov 29, 2016 at 08:37:50PM +0100, Greg KH <gregkh@linuxfoundation.org> wrote:
> > On Tue, Nov 29, 2016 at 01:38:20AM +0300, Serge Semin wrote:
> > > +struct idt_89hpesx_dev {
> > > +	u32 eesize;
> > > +	bool eero;
> > > +	u8 eeaddr;
> > > +
> > > +	u8 inieecmd;
> > > +	u8 inicsrcmd;
> > > +	u8 iniccode;
> > > +
> > > +	atomic_t csr;
> > > +
> > > +	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
> > > +	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
> > > +	struct mutex smb_mtx;
> > > +
> > > +	struct i2c_client *client;
> > > +
> > > +	struct bin_attribute *ee_file;
> > > +	struct dentry *csr_dir;
> > > +	struct dentry *csr_file;
> > > +};
> > > +#define to_pdev_kobj(__kobj) \
> > > +	dev_get_drvdata(container_of(__kobj, struct device, kobj))
> > 
> > Is it a struct device, or a kobject?  This is totally confusing to me.
> > 
> > And can't you just use kobj_to_dev()?
> > 
> 
> I just didn't know about kobj_to_dev() inline function. Totally agree that
> container_of() should be replaced with it.
> What does look confusing to you? Do you mean the name "to_pdev_kobj" of the
> macro?

Yes, the macro is odd.  As you are doing two different things here, just
spell it out in the code and use kobj_to_dev() to make it easier to
read please.

> > > +/*
> > > + * eeprom_attribute - EEPROM sysfs-node attributes
> > > + *
> > > + * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
> > > + * be read-only as well if the corresponding flag is specified in OF node.
> > > + */
> > > +BIN_ATTR(eeprom, 0644, idt_sysfs_eeprom_read, idt_sysfs_eeprom_write,
> > > +	 EEPROM_DEF_SIZE);
> > 
> > static?
> > 
> > And BIN_ATTR_RW()?
> > 
> > thanks,
> > 
> > greg k-h
> 
> Of course it should be static. Thanks for noticing that.
> But I intentionally utilized BIN_ATTR() instead of BIN_ATTR_RW(), because
> the last one implies to define the read/write methods with names
> "_name##_read"/"_name##_write", which totally get out of naming within the
> driver source code.

That's ok, use the names the macro wants you to, that's the best way,
and it ensures that I don't have to audit your permissions are correct
for the file.

> To tell the truth macro BIN_ATTR_RW() isn't that popular in the
> kernel.

Yes, but it should be, I have patches floating around somewhere to fix
almost all of these up.

> Neither is BIN_ATTR() macro, but it suites my driver better than the
> another one.

a "raw" BIN_ATTR() shouldn't be used either, please use the _RW()
variant.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-11-29 21:24         ` Greg KH
@ 2016-11-29 21:45           ` Serge Semin
  0 siblings, 0 replies; 34+ messages in thread
From: Serge Semin @ 2016-11-29 21:45 UTC (permalink / raw)
  To: Greg KH
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Tue, Nov 29, 2016 at 10:24:12PM +0100, Greg KH <gregkh@linuxfoundation.org> wrote:
> On Wed, Nov 30, 2016 at 12:16:25AM +0300, Serge Semin wrote:
> > On Tue, Nov 29, 2016 at 08:37:50PM +0100, Greg KH <gregkh@linuxfoundation.org> wrote:
> > > On Tue, Nov 29, 2016 at 01:38:20AM +0300, Serge Semin wrote:
> > > > +struct idt_89hpesx_dev {
> > > > +	u32 eesize;
> > > > +	bool eero;
> > > > +	u8 eeaddr;
> > > > +
> > > > +	u8 inieecmd;
> > > > +	u8 inicsrcmd;
> > > > +	u8 iniccode;
> > > > +
> > > > +	atomic_t csr;
> > > > +
> > > > +	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
> > > > +	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
> > > > +	struct mutex smb_mtx;
> > > > +
> > > > +	struct i2c_client *client;
> > > > +
> > > > +	struct bin_attribute *ee_file;
> > > > +	struct dentry *csr_dir;
> > > > +	struct dentry *csr_file;
> > > > +};
> > > > +#define to_pdev_kobj(__kobj) \
> > > > +	dev_get_drvdata(container_of(__kobj, struct device, kobj))
> > > 
> > > Is it a struct device, or a kobject?  This is totally confusing to me.
> > > 
> > > And can't you just use kobj_to_dev()?
> > > 
> > 
> > I just didn't know about kobj_to_dev() inline function. Totally agree that
> > container_of() should be replaced with it.
> > What does look confusing to you? Do you mean the name "to_pdev_kobj" of the
> > macro?
> 
> Yes, the macro is odd.  As you are doing two different things here, just
> spell it out in the code and use kobj_to_dev() to make it easier to
> read please.
> 
> > > > +/*
> > > > + * eeprom_attribute - EEPROM sysfs-node attributes
> > > > + *
> > > > + * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
> > > > + * be read-only as well if the corresponding flag is specified in OF node.
> > > > + */
> > > > +BIN_ATTR(eeprom, 0644, idt_sysfs_eeprom_read, idt_sysfs_eeprom_write,
> > > > +	 EEPROM_DEF_SIZE);
> > > 
> > > static?
> > > 
> > > And BIN_ATTR_RW()?
> > > 
> > > thanks,
> > > 
> > > greg k-h
> > 
> > Of course it should be static. Thanks for noticing that.
> > But I intentionally utilized BIN_ATTR() instead of BIN_ATTR_RW(), because
> > the last one implies to define the read/write methods with names
> > "_name##_read"/"_name##_write", which totally get out of naming within the
> > driver source code.
> 
> That's ok, use the names the macro wants you to, that's the best way,
> and it ensures that I don't have to audit your permissions are correct
> for the file.
> 
> > To tell the truth macro BIN_ATTR_RW() isn't that popular in the
> > kernel.
> 
> Yes, but it should be, I have patches floating around somewhere to fix
> almost all of these up.
> 
> > Neither is BIN_ATTR() macro, but it suites my driver better than the
> > another one.
> 
> a "raw" BIN_ATTR() shouldn't be used either, please use the _RW()
> variant.
> 
> thanks,
> 
> greg k-h

Agreed with all the notes. I will send patchset v3 within next hour.

Thanks,
-Sergey

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-11-28 22:38 ` [PATCH v2 0/2] eeprom: " Serge Semin
  2016-11-28 22:38   ` [PATCH v2 1/2] " Serge Semin
  2016-11-28 22:38   ` [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
@ 2016-11-29 22:27   ` Serge Semin
  2016-11-29 22:27     ` [PATCH v3 1/2] " Serge Semin
                       ` (2 more replies)
  2 siblings, 3 replies; 34+ messages in thread
From: Serge Semin @ 2016-11-29 22:27 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

Changelog v3:
- Get rid of dev_*_idt() macros
- Replace to_pdev_kobj() macro with naked dev_get_drvdata() call
- Return naked 0 instead of SUCCESS macro
- IDT CSR debug file is moved to debugfs
- BIN_ATTR_RW is used to declare sysfs binary attribute
- Moved bindings file to a separate patch
- Need to create a specific bin_attribute structure for each device
- Perform a few read retries with delays if EEPROM is busy

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>

Serge Semin (2):
  eeprom: Add IDT 89HPESx EEPROM/CSR driver
  eeprom: Add IDT 89HPESx driver dts-binding file

 .../devicetree/bindings/misc/idt_89hpesx.txt       |   41 +
 drivers/misc/eeprom/Kconfig                        |   10 +
 drivers/misc/eeprom/Makefile                       |    1 +
 drivers/misc/eeprom/idt_89hpesx.c                  | 1574 ++++++++++++++++++++
 4 files changed, 1626 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

-- 
2.6.6

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-11-29 22:27   ` [PATCH v3 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
@ 2016-11-29 22:27     ` Serge Semin
  2016-11-29 22:27     ` [PATCH v3 2/2] eeprom: Add IDT 89HPESx driver dts-binding file Serge Semin
  2016-12-13 14:22     ` [PATCH v4 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2 siblings, 0 replies; 34+ messages in thread
From: Serge Semin @ 2016-11-29 22:27 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

  This driver provides an access to EEPROM of IDT PCIe-switches. IDT PCIe-
switches expose a simple SMBus interface to perform IO-operations from/to
EEPROM, which is located at private (so called Master) SMBus. The driver
creates a simple binary sysfs-file to have an access to the EEPROM using
the SMBus-slave interface in the i2c-device susfs-directory.
  Additionally IDT 89HPESx SMBus interface has an ability to read/write
values of device CSRs. This driver exposes debugfs-file to perform simple
IO-operations using that ability for just basic debug purpose.

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
 drivers/misc/eeprom/Kconfig       |   10 +
 drivers/misc/eeprom/Makefile      |    1 +
 drivers/misc/eeprom/idt_89hpesx.c | 1574 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1585 insertions(+)
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index c4e41c2..de58762 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -100,4 +100,14 @@ config EEPROM_DIGSY_MTC_CFG
 
 	  If unsure, say N.
 
+config EEPROM_IDT_89HPESX
+	tristate "IDT 89HPESx PCIe-swtiches EEPROM / CSR support"
+	depends on I2C && SYSFS
+	help
+	  Enable this driver to get read/write access to EEPROM / CSRs
+	  over IDT PCIe-swtich i2c-slave interface.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called idt_89hpesx.
+
 endmenu
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index fc1e81d..90a5262 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o
 obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
 obj-$(CONFIG_EEPROM_93XX46)	+= eeprom_93xx46.o
 obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
+obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c
index 0000000..a54c0f4
--- /dev/null
+++ b/drivers/misc/eeprom/idt_89hpesx.c
@@ -0,0 +1,1574 @@
+/*
+ *   This file is provided under a GPLv2 license.  When using or
+ *   redistributing this file, you may do so under that license.
+ *
+ *   GPL LICENSE SUMMARY
+ *
+ *   Copyright (C) 2016 T-Platforms. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or modify it
+ *   under the terms and conditions of the GNU General Public License,
+ *   version 2, as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful, but WITHOUT
+ *   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ *   more details.
+ *
+ *   You should have received a copy of the GNU General Public License along
+ *   with this program; if not, it can be found <http://www.gnu.org/licenses/>.
+ *
+ *   The full GNU General Public License is included in this distribution in
+ *   the file called "COPYING".
+ *
+ *   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.
+ *
+ * IDT PCIe-switch NTB Linux driver
+ *
+ * Contact Information:
+ * Serge Semin <fancer.lancer@gmail.com>, <Sergey.Semin@t-platforms.ru>
+ */
+/*
+ *           NOTE of the IDT 89HPESx SMBus-slave interface driver
+ *    This driver primarily is developed to have an access to EEPROM device of
+ * IDT PCIe-switches. IDT provides a simple SMBus interface to perform IO-
+ * operations from/to EEPROM, which is located at private (so called Master)
+ * SMBus of switches. Using that interface this the driver creates a simple
+ * binary sysfs-file in the device directory:
+ * /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
+ * In case if read-only flag is specified in the dts-node of device desription,
+ * User-space applications won't be able to write to the EEPROM sysfs-node.
+ *    Additionally IDT 89HPESx SMBus interface has an ability to write/read
+ * data of device CSRs. This driver exposes debugf-file to perform simple IO
+ * operations using that ability for just basic debug purpose. Particularly
+ * next file is created in the specific debugfs-directory:
+ * /sys/kernel/debug/idt_csr/
+ * Format of the debugfs-node is:
+ * $ cat /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * <CSR address>:<CSR value>
+ * So reading the content of the file gives current CSR address and it value.
+ * If User-space application wishes to change current CSR address,
+ * it can just write a proper value to the sysfs-file:
+ * $ echo "<CSR address>" > /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>
+ * If it wants to change the CSR value as well, the format of the write
+ * operation is:
+ * $ echo "<CSR address>:<CSR value>" > \
+ *        /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * CSR address and value can be any of hexadecimal, decimal or octal format.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/debugfs.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/pci_ids.h>
+#include <linux/delay.h>
+
+#define IDT_NAME		"89hpesx"
+#define IDT_89HPESX_DESC	"IDT 89HPESx SMBus-slave interface driver"
+#define IDT_89HPESX_VER		"1.0"
+
+MODULE_DESCRIPTION(IDT_89HPESX_DESC);
+MODULE_VERSION(IDT_89HPESX_VER);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("T-platforms");
+
+/*
+ * csr_dbgdir - CSR read/write operations Debugfs directory
+ */
+static struct dentry *csr_dbgdir;
+
+/*
+ * struct idt_89hpesx_dev - IDT 89HPESx device data structure
+ * @eesize:	Size of EEPROM in bytes (calculated from "idt,eecompatible")
+ * @eero:	EEPROM Read-only flag
+ * @eeaddr:	EEPROM custom address
+ *
+ * @inieecmd:	Initial cmd value for EEPROM read/write operations
+ * @inicsrcmd:	Initial cmd value for CSR read/write operations
+ * @iniccode:	Initialial command code value for IO-operations
+ *
+ * @csr:	CSR address to perform read operation
+ *
+ * @smb_write:	SMBus write method
+ * @smb_read:	SMBus read method
+ * @smb_mtx:	SMBus mutex
+ *
+ * @client:	i2c client used to perform IO operations
+ *
+ * @ee_file:	EEPROM read/write sysfs-file
+ * @csr_file:	CSR read/write debugfs-node
+ */
+struct idt_smb_seq;
+struct idt_89hpesx_dev {
+	u32 eesize;
+	bool eero;
+	u8 eeaddr;
+
+	u8 inieecmd;
+	u8 inicsrcmd;
+	u8 iniccode;
+
+	atomic_t csr;
+
+	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
+	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
+	struct mutex smb_mtx;
+
+	struct i2c_client *client;
+
+	struct bin_attribute *ee_file;
+	struct dentry *csr_dir;
+	struct dentry *csr_file;
+};
+
+/*
+ * struct idt_smb_seq - sequence of data to be read/written from/to IDT 89HPESx
+ * @ccode:	SMBus command code
+ * @bytecnt:	Byte count of operation
+ * @data:	Data to by written
+ */
+struct idt_smb_seq {
+	u8 ccode;
+	u8 bytecnt;
+	u8 *data;
+};
+
+/*
+ * struct idt_eeprom_seq - sequence of data to be read/written from/to EEPROM
+ * @cmd:	Transaction CMD
+ * @eeaddr:	EEPROM custom address
+ * @memaddr:	Internal memory address of EEPROM
+ * @data:	Data to be written at the memory address
+ */
+struct idt_eeprom_seq {
+	u8 cmd;
+	u8 eeaddr;
+	u16 memaddr;
+	u8 data;
+} __packed;
+
+/*
+ * struct idt_csr_seq - sequence of data to be read/written from/to CSR
+ * @cmd:	Transaction CMD
+ * @csraddr:	Internal IDT device CSR address
+ * @data:	Data to be read/written from/to the CSR address
+ */
+struct idt_csr_seq {
+	u8 cmd;
+	u16 csraddr;
+	u32 data;
+} __packed;
+
+/*
+ * SMBus command code macros
+ * @CCODE_END:		Indicates the end of transaction
+ * @CCODE_START:	Indicates the start of transaction
+ * @CCODE_CSR:		CSR read/write transaction
+ * @CCODE_EEPROM:	EEPROM read/write transaction
+ * @CCODE_BYTE:		Supplied data has BYTE length
+ * @CCODE_WORD:		Supplied data has WORD length
+ * @CCODE_BLOCK:	Supplied data has variable length passed in bytecnt
+ *			byte right following CCODE byte
+ */
+#define CCODE_END	((u8)0x01)
+#define CCODE_START	((u8)0x02)
+#define CCODE_CSR	((u8)0x00)
+#define CCODE_EEPROM	((u8)0x04)
+#define CCODE_BYTE	((u8)0x00)
+#define CCODE_WORD	((u8)0x20)
+#define CCODE_BLOCK	((u8)0x40)
+#define CCODE_PEC	((u8)0x80)
+
+/*
+ * EEPROM command macros
+ * @EEPROM_OP_WRITE:	EEPROM write operation
+ * @EEPROM_OP_READ:	EEPROM read operation
+ * @EEPROM_USA:		Use specified address of EEPROM
+ * @EEPROM_NAERR:	EEPROM device is not ready to respond
+ * @EEPROM_LAERR:	EEPROM arbitration loss error
+ * @EEPROM_MSS:		EEPROM misplace start & stop bits error
+ * @EEPROM_WR_CNT:	Bytes count to perform write operation
+ * @EEPROM_WRRD_CNT:	Bytes count to write before reading
+ * @EEPROM_RD_CNT:	Bytes count to perform read operation
+ * @EEPROM_DEF_SIZE:	Fall back size of EEPROM
+ * @EEPROM_DEF_ADDR:	Defatul EEPROM address
+ * @EEPROM_TOUT:	Timeout before retry read operation if eeprom is busy
+ */
+#define EEPROM_OP_WRITE	((u8)0x00)
+#define EEPROM_OP_READ	((u8)0x01)
+#define EEPROM_USA	((u8)0x02)
+#define EEPROM_NAERR	((u8)0x08)
+#define EEPROM_LAERR    ((u8)0x10)
+#define EEPROM_MSS	((u8)0x20)
+#define EEPROM_WR_CNT	((u8)5)
+#define EEPROM_WRRD_CNT	((u8)4)
+#define EEPROM_RD_CNT	((u8)5)
+#define EEPROM_DEF_SIZE	((u16)4096)
+#define EEPROM_DEF_ADDR	((u8)0x50)
+#define EEPROM_TOUT	(100)
+
+/*
+ * CSR command macros
+ * @CSR_DWE:		Enable all four bytes of the operation
+ * @CSR_OP_WRITE:	CSR write operation
+ * @CSR_OP_READ:	CSR read operation
+ * @CSR_RERR:		Read operation error
+ * @CSR_WERR:		Write operation error
+ * @CSR_WR_CNT:		Bytes count to perform write operation
+ * @CSR_WRRD_CNT:	Bytes count to write before reading
+ * @CSR_RD_CNT:		Bytes count to perform read operation
+ * @CSR_MAX:		Maximum CSR address
+ * @CSR_DEF:		Default CSR address
+ * @CSR_REAL_ADDR:	CSR real unshifted address
+ */
+#define CSR_DWE			((u8)0x0F)
+#define CSR_OP_WRITE		((u8)0x00)
+#define CSR_OP_READ		((u8)0x10)
+#define CSR_RERR		((u8)0x40)
+#define CSR_WERR		((u8)0x80)
+#define CSR_WR_CNT		((u8)7)
+#define CSR_WRRD_CNT		((u8)3)
+#define CSR_RD_CNT		((u8)7)
+#define CSR_MAX			((u32)0x3FFFF)
+#define CSR_DEF			((u16)0x0000)
+#define CSR_REAL_ADDR(val)	((unsigned int)val << 2)
+
+/*
+ * IDT 89HPESx basic register
+ * @IDT_VIDDID_CSR:	PCIe VID and DID of IDT 89HPESx
+ * @IDT_VID_MASK:	Mask of VID
+ */
+#define IDT_VIDDID_CSR	((u32)0x0000)
+#define IDT_VID_MASK	((u32)0xFFFF)
+
+/*
+ * IDT 89HPESx can send NACK when new command is sent before previous one
+ * fininshed execution. In this case driver retries operation
+ * certain times.
+ * @RETRY_CNT:		Number of retries before giving up and fail
+ * @idt_smb_safe:	Generate a retry loop on corresponding SMBus method
+ */
+#define RETRY_CNT (128)
+#define idt_smb_safe(ops, args...) ({ \
+	int __retry = RETRY_CNT; \
+	s32 __sts; \
+	do { \
+		__sts = i2c_smbus_ ## ops ## _data(args); \
+	} while (__retry-- && __sts < 0); \
+	__sts; \
+})
+
+/*===========================================================================
+ *                         i2c bus level IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_smb_write_byte() - SMBus write method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_byte(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied data sending byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Send data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_byte() - SMBus read method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_byte(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied buffer receiving byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Read data from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_word() - SMBus write method when I2C_SMBUS_BYTE_DATA and
+ *                        I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_word(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data sending two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Send word data to the device */
+		sts = idt_smb_safe(write_word, pdev->client, ccode,
+			*(u16 *)&seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	/* If there is odd number of bytes then send just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Send byte data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_word() - SMBus read method when I2C_SMBUS_BYTE_DATA and
+ *                       I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_word(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data reading two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Read word data from the device */
+		sts = idt_smb_safe(read_word, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		*(u16 *)&seq->data[idx] = (u16)sts;
+	}
+
+	/* If there is odd number of bytes then receive just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Read last data byte from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_block() - SMBus write method when I2C_SMBUS_BLOCK_DATA
+ *                         operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_block(struct idt_89hpesx_dev *pdev,
+			       const struct idt_smb_seq *seq)
+{
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send block of data to the device */
+	return idt_smb_safe(write_block, pdev->client, ccode, seq->bytecnt,
+		seq->data);
+}
+
+/*
+ * idt_smb_read_block() - SMBus read method when I2C_SMBUS_BLOCK_DATA
+ *                        operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_block(struct idt_89hpesx_dev *pdev,
+			      struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read block of data from the device */
+	sts = idt_smb_safe(read_block, pdev->client, ccode, seq->data);
+	if (sts != seq->bytecnt)
+		return (sts < 0 ? sts : -ENODATA);
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_i2c_block() - SMBus write method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                             operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ *
+ * NOTE It's usual SMBus write block operation, except the actual data length is
+ * sent as first byte of data
+ */
+static int idt_smb_write_i2c_block(struct idt_89hpesx_dev *pdev,
+				   const struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the data to send. Length byte must be added prior the data */
+	buf[0] = seq->bytecnt;
+	memcpy(&buf[1], seq->data, seq->bytecnt);
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send length and block of data to the device */
+	return idt_smb_safe(write_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+}
+
+/*
+ * idt_smb_read_i2c_block() - SMBus read method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                            operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ *
+ * NOTE It's usual SMBus read block operation, except the actual data length is
+ * retrieved as first byte of data
+ */
+static int idt_smb_read_i2c_block(struct idt_89hpesx_dev *pdev,
+				  struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+	s32 sts;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read length and block of data from the device */
+	sts = idt_smb_safe(read_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+	if (sts != seq->bytecnt + 1)
+		return (sts < 0 ? sts : -ENODATA);
+	if (buf[0] != seq->bytecnt)
+		return -ENODATA;
+
+	/* Copy retrieved data to the output data buffer */
+	memcpy(seq->data, &buf[1], seq->bytecnt);
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          EEPROM IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_eeprom_read_byte() - read just one byte from EEPROM
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_read_byte(struct idt_89hpesx_dev *pdev, u16 memaddr,
+				u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret, retry;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/*
+	 * Sometimes EEPROM may respond with NACK if it's busy with previous
+	 * operation, so we need to perform a few attempts of read cycle
+	 */
+	retry = RETRY_CNT;
+	do {
+		/* Send EEPROM memory address to read data from */
+		smbseq.bytecnt = EEPROM_WRRD_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to init eeprom addr 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Perform read operation */
+		smbseq.bytecnt = EEPROM_RD_CNT;
+		ret = pdev->smb_read(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to read eeprom data 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Restart read operation if the device is busy */
+		if (retry && (eeseq.cmd & EEPROM_NAERR)) {
+			dev_dbg(dev, "EEPROM busy, retry reading after %d ms",
+				EEPROM_TOUT);
+			msleep(EEPROM_TOUT);
+			continue;
+		}
+
+		/* Check whether IDT successfully read data from EEPROM */
+		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
+			dev_err(dev,
+				"Communication with eeprom failed, cmd 0x%hhx",
+				eeseq.cmd);
+			ret = -EREMOTEIO;
+			break;
+		}
+
+		/* Save retrieved data and exit the loop */
+		*data = eeseq.data;
+		break;
+	} while (retry--);
+
+	/* Return the status of operation */
+	return ret;
+}
+
+/*
+ * idt_eeprom_write() - EEPROM write operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to be written
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_write(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			    const u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+	u16 idx;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/* Send data byte-by-byte, checking if it is successfully written */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Perform write operation */
+		smbseq.bytecnt = EEPROM_WR_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_WRITE;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		eeseq.data = data[idx];
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev,
+				"Failed to write 0x%04hx:0x%02hhx to eeprom",
+				memaddr, data[idx]);
+			goto err_mutex_unlock;
+		}
+
+		/*
+		 * Check whether the data is successfully written by reading
+		 * from the same EEPROM memory address.
+		 */
+		eeseq.data = ~data[idx];
+		ret = idt_eeprom_read_byte(pdev, memaddr, &eeseq.data);
+		if (ret != 0)
+			goto err_mutex_unlock;
+
+		/* Check whether the read byte is the same as written one */
+		if (eeseq.data != data[idx]) {
+			dev_err(dev, "Values don't match 0x%02hhx != 0x%02hhx",
+				eeseq.data, data[idx]);
+			ret = -EREMOTEIO;
+			goto err_mutex_unlock;
+		}
+
+		/* Unlock IDT SMBus device */
+err_mutex_unlock:
+		mutex_unlock(&pdev->smb_mtx);
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_eeprom_read() - EEPROM read operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to read
+ * @buf:	Buffer to read data to
+ */
+static int idt_eeprom_read(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			   u8 *buf)
+{
+	int ret;
+	u16 idx;
+
+	/* Read data byte-by-byte, retrying if it wasn't successful */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Just read the byte to the buffer */
+		ret = idt_eeprom_read_byte(pdev, memaddr, &buf[idx]);
+
+		/* Unlock IDT SMBus device */
+		mutex_unlock(&pdev->smb_mtx);
+
+		/* Return error if read operation failed */
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          CSR IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_csr_write() - CSR write operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_write(struct idt_89hpesx_dev *pdev, u16 csraddr,
+			 const u32 data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Perform write operation */
+	smbseq.bytecnt = CSR_WR_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_WRITE;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	csrseq.data = cpu_to_le32(data);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write 0x%04x: 0x%04x to csr",
+			CSR_REAL_ADDR(csraddr), data);
+		goto err_mutex_unlock;
+	}
+
+	/* Send CSR address to read data from */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*
+ * idt_csr_read() - CSR read operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_read(struct idt_89hpesx_dev *pdev, u16 csraddr, u32 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Send CSR register address before reading it */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04hx",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Save data retrieved from IDT */
+	*data = le32_to_cpu(csrseq.data);
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*===========================================================================
+ *                          Sysfs/debugfs-nodes IO-operations
+ *===========================================================================
+ */
+
+/*
+ * eeprom_write() - EEPROM sysfs-node write callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t eeprom_write(struct file *filp, struct kobject *kobj,
+			    struct bin_attribute *attr,
+			    char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(kobj_to_dev(kobj));
+
+	/* Perform EEPROM write operation */
+	ret = idt_eeprom_write(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * eeprom_read() - EEPROM sysfs-node read callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
+			   struct bin_attribute *attr,
+			   char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(kobj_to_dev(kobj));
+
+	/* Perform EEPROM read operation */
+	ret = idt_eeprom_read(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_write() - CSR debugfs-node write callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to read data from
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It accepts either "0x<reg addr>:0x<value>" for saving register address
+ * and writing value to specified DWORD register or "0x<reg addr>" for
+ * just saving register address in order to perform next read operation.
+ *
+ * WARNING No spaces are allowed. Incoming string must be strictly formated as:
+ * "<reg addr>:<value>". Register address must be aligned within 4 bytes
+ * (one DWORD).
+ */
+static ssize_t idt_dbgfs_csr_write(struct file *filep, const char __user *ubuf,
+				   size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	char *colon_ch, *csraddr_str, *csrval_str;
+	int ret, csraddr_len, csrval_len;
+	u32 csraddr, csrval;
+	char *buf;
+
+	/* Copy data from User-space */
+	buf = kmalloc(count + 1, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = simple_write_to_buffer(buf, count, offp, ubuf, count);
+	if (ret < 0)
+		goto free_buf;
+	buf[count] = 0;
+
+	/* Find position of colon in the buffer */
+	colon_ch = strnchr(buf, count, ':');
+
+	/*
+	 * If there is colon passed then new CSR value should be parsed as
+	 * well, so allocate buffer for CSR address substring.
+	 * If no colon is found, then string must have just one number with
+	 * no new CSR value
+	 */
+	if (colon_ch != NULL) {
+		csraddr_len = colon_ch - buf;
+		csraddr_str =
+			kmalloc(sizeof(char)*(csraddr_len + 1), GFP_KERNEL);
+		if (csraddr_str == NULL)
+			return -ENOMEM;
+		/* Copy the register address to the substring buffer */
+		strncpy(csraddr_str, buf, csraddr_len);
+		csraddr_str[csraddr_len] = '\0';
+		/* Register value must follow the colon */
+		csrval_str = colon_ch + 1;
+		csrval_len = strnlen(csrval_str, count - csraddr_len);
+	} else /* if (str_colon == NULL) */ {
+		csraddr_str = (char *)buf; /* Just to shut warning up */
+		csraddr_len = strnlen(csraddr_str, count);
+		csrval_str = NULL;
+		csrval_len = 0;
+	}
+
+	/* Convert CSR address to u32 value */
+	ret = kstrtou32(csraddr_str, 0, &csraddr);
+	if (ret != 0)
+		goto free_csraddr_str;
+
+	/* Check whether passed register address is valid */
+	if (csraddr > CSR_MAX || !IS_ALIGNED(csraddr, SZ_4)) {
+		ret = -EINVAL;
+		goto free_csraddr_str;
+	}
+
+	/* Shift register address to the right so to have u16 address */
+	csraddr >>= 2;
+
+	/* Parse new CSR value and send it to IDT, if colon has been found */
+	if (colon_ch != NULL) {
+		ret = kstrtou32(csrval_str, 0, &csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+
+		ret = idt_csr_write(pdev, (u16)csraddr, csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+	}
+
+	/* Save CSR address in the data structure for future read operations */
+	atomic_set(&pdev->csr, (int)csraddr);
+
+	/* Free memory only if colon has been found */
+free_csraddr_str:
+	if (colon_ch != NULL)
+		kfree(csraddr_str);
+
+	/* Free buffer allocated for data retrieved from User-space */
+free_buf:
+	kfree(buf);
+
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_read() - CSR debugfs-node read callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to write data to
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It just prints the pair "0x<reg addr>:0x<value>" to passed buffer.
+ */
+#define CSRBUF_SIZE	((size_t)32)
+static ssize_t idt_dbgfs_csr_read(struct file *filep, char __user *ubuf,
+				  size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	u32 csraddr, csrval;
+	char buf[CSRBUF_SIZE];
+	int ret, size;
+
+	/* Read current CSR address */
+	csraddr = atomic_read(&pdev->csr);
+
+	/* Perform CSR read operation */
+	ret = idt_csr_read(pdev, (u16)csraddr, &csrval);
+	if (ret != 0)
+		return ret;
+
+	/* Shift register address to the left so to have real address */
+	csraddr <<= 2;
+
+	/* Print the "0x<reg addr>:0x<value>" to buffer */
+	size = snprintf(buf, CSRBUF_SIZE, "0x%05x:0x%08x\n",
+		(unsigned int)csraddr, (unsigned int)csrval);
+
+	/* Copy data to User-space */
+	return simple_read_from_buffer(ubuf, count, offp, buf, size);
+}
+
+/*
+ * eeprom_attribute - EEPROM sysfs-node attributes
+ *
+ * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
+ * be read-only as well if the corresponding flag is specified in OF node.
+ */
+static BIN_ATTR_RW(eeprom, EEPROM_DEF_SIZE);
+
+/*
+ * csr_dbgfs_ops - CSR debugfs-node read/write operations
+ */
+static const struct file_operations csr_dbgfs_ops = {
+	.owner = THIS_MODULE,
+	.open = simple_open,
+	.write = idt_dbgfs_csr_write,
+	.read = idt_dbgfs_csr_read
+};
+
+/*===========================================================================
+ *                       Driver init/deinit methods
+ *===========================================================================
+ */
+
+/*
+ * idt_set_defval() - set default device data parameters
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_set_defval(struct idt_89hpesx_dev *pdev)
+{
+	/* If OF info is missing then use next values */
+	pdev->eesize = EEPROM_DEF_SIZE;
+	pdev->eero = true;
+	pdev->inieecmd = 0;
+	pdev->eeaddr = EEPROM_DEF_ADDR << 1;
+}
+
+#ifdef CONFIG_OF
+/*
+ * idt_get_ofdata() - get IDT i2c-device parameters from device tree
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	struct device_node *node = pdev->client->dev.of_node;
+	struct device *dev = &pdev->client->dev;
+	const __be32 *val;
+
+	/* Read dts node parameters */
+	if (node) {
+		/* Get EEPROM size from 'idt,eesize' */
+		val = of_get_property(node, "idt,eesize", NULL);
+		if (val != NULL) {
+			pdev->eesize = be32_to_cpup(val);
+			if (!is_power_of_2(pdev->eesize))
+				dev_warn(dev,
+					"EEPROM size %u is not power of 2",
+					pdev->eesize);
+		} else /* if (val == NULL) */ {
+			pdev->eesize = EEPROM_DEF_SIZE;
+			dev_warn(dev, "No EEPROM size, set default %u bytes",
+				pdev->eesize);
+		}
+
+		/* Get custom EEPROM address from 'idt,eeaddr' */
+		val = of_get_property(node, "idt,eeaddr", NULL);
+		if (val != NULL) {
+			pdev->inieecmd = EEPROM_USA;
+			pdev->eeaddr = be32_to_cpup(val) << 1;
+		} else /* if (val != NULL) */ {
+			pdev->inieecmd = 0;
+			pdev->eeaddr = EEPROM_DEF_ADDR << 1;
+		}
+
+		/* Check EEPROM 'read-only' flag */
+		if (of_get_property(node, "read-only", NULL))
+			pdev->eero = true;
+		else /* if (!of_get_property(node, "read-only", NULL)) */
+			pdev->eero = false;
+	} else {
+		dev_warn(dev, "No dts node, set default values");
+		idt_set_defval(pdev);
+	}
+}
+#else
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	dev_warn(dev, "OF table is unsupported, set default values");
+
+	/* Nothing we can do, just set the default values */
+	idt_set_defval(pdev);
+}
+#endif /* CONFIG_OF */
+
+/*
+ * idt_create_pdev() - create and init data structure of the driver
+ * @client:	i2c client of IDT PCIe-switch device
+ */
+static struct idt_89hpesx_dev *idt_create_pdev(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev;
+
+	/* Allocate memory for driver data */
+	pdev = devm_kmalloc(&client->dev, sizeof(struct idt_89hpesx_dev),
+		GFP_KERNEL);
+	if (pdev == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	/* Initialize basic fields of the data */
+	pdev->client = client;
+	i2c_set_clientdata(client, pdev);
+
+	/* Read OF nodes information */
+	idt_get_ofdata(pdev);
+
+	/* Initialize basic CSR CMD field - use full DWORD-sized r/w ops */
+	pdev->inicsrcmd = CSR_DWE;
+	atomic_set(&pdev->csr, CSR_DEF);
+
+	/* Enable Packet Error Checking if it's supported by adapter */
+	if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) {
+		pdev->iniccode = CCODE_PEC;
+		client->flags |= I2C_CLIENT_PEC;
+	} else /* PEC is unsupported */ {
+		pdev->iniccode = 0;
+	}
+
+	dev_dbg(&client->dev, "IDT 89HPESx data created");
+
+	return pdev;
+}
+
+/*
+ * idt_free_pdev() - free data structure of the driver
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_free_pdev(struct idt_89hpesx_dev *pdev)
+{
+	/* Clear driver data from device private field */
+	i2c_set_clientdata(pdev->client, NULL);
+
+	/* Just free memory allocated for data */
+	devm_kfree(&pdev->client->dev, pdev);
+
+	dev_dbg(&pdev->client->dev, "IDT 89HPESx data discarded");
+}
+
+/*
+ * idt_set_smbus_ops() - set supported SMBus operations
+ * @pdev:	Pointer to the driver data
+ * Return status of smbus check operations
+ */
+static int idt_set_smbus_ops(struct idt_89hpesx_dev *pdev)
+{
+	struct i2c_adapter *adapter = pdev->client->adapter;
+	struct device *dev = &pdev->client->dev;
+
+	/* Check i2c adapter read functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_READ_BLOCK_DATA)) {
+		pdev->smb_read = idt_smb_read_block;
+		dev_dbg(dev, "SMBus block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+		pdev->smb_read = idt_smb_read_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_word;
+		dev_warn(dev, "Use slow word/byte SMBus read ops");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_byte;
+		dev_warn(dev, "Use slow byte SMBus read op");
+	} else /* no supported smbus read operations */ {
+		dev_err(dev, "No supported SMBus read op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Check i2c adapter write functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) {
+		pdev->smb_write = idt_smb_write_block;
+		dev_dbg(dev, "SMBus block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
+		pdev->smb_write = idt_smb_write_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_word;
+		dev_warn(dev, "Use slow word/byte SMBus write op");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_byte;
+		dev_warn(dev, "Use slow byte SMBus write op");
+	} else /* no supported smbus write operations */ {
+		dev_err(dev, "No supported SMBus write op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Initialize IDT SMBus slave interface mutex */
+	mutex_init(&pdev->smb_mtx);
+
+	dev_dbg(dev, "SMBus functionality successfully checked");
+
+	return 0;
+}
+
+/*
+ * idt_check_dev() - check whether it's really IDT 89HPESx device
+ * @pdev:	Pointer to the driver data
+ * Return status of i2c adapter check operation
+ */
+static int idt_check_dev(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	u32 viddid;
+	int ret;
+
+	/* Read VID and DID directly from IDT memory space */
+	ret = idt_csr_read(pdev, IDT_VIDDID_CSR, &viddid);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read VID/DID");
+		return ret;
+	}
+
+	/* Check whether it's IDT device */
+	if ((viddid & IDT_VID_MASK) != PCI_VENDOR_ID_IDT) {
+		dev_err(dev, "Got unsupported VID/DID: 0x%08x", viddid);
+		return -ENODEV;
+	}
+
+	dev_info(dev, "Found IDT 89HPES device VID:0x%04x, DID:0x%04x",
+		(viddid & IDT_VID_MASK), (viddid >> 16));
+
+	return 0;
+}
+
+/*
+ * idt_create_sysfs_files() - create sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+static int idt_create_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	int ret;
+
+	/* Allocate memory for attribute file */
+	pdev->ee_file = devm_kmalloc(dev, sizeof(*pdev->ee_file), GFP_KERNEL);
+	if (!pdev->ee_file)
+		return -ENOMEM;
+
+	/* Copy the declared EEPROM attr structure to change some of fields */
+	memcpy(pdev->ee_file, &bin_attr_eeprom, sizeof(*pdev->ee_file));
+
+	/* In case of read-only EEPROM get rid of write ability */
+	if (pdev->eero) {
+		pdev->ee_file->attr.mode &= ~0200;
+		pdev->ee_file->write = NULL;
+	}
+	/* Create EEPROM sysfs file */
+	pdev->ee_file->size = pdev->eesize;
+	ret = sysfs_create_bin_file(&dev->kobj, pdev->ee_file);
+	if (ret != 0) {
+		kfree(pdev->ee_file);
+		dev_err(dev, "Failed to create EEPROM sysfs-node");
+		return ret;
+	}
+
+	dev_dbg(dev, "Sysfs-files created");
+
+	return 0;
+}
+
+/*
+ * idt_remove_sysfs_files() - remove sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	/* Remove EEPROM sysfs file */
+	sysfs_remove_bin_file(&dev->kobj, pdev->ee_file);
+
+	/* Free memory allocated for bin_attribute structure */
+	kfree(pdev->ee_file);
+
+	dev_dbg(dev, "Sysfs-files removed");
+}
+
+/*
+ * idt_create_dbgfs_files() - create debugfs files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+#define CSRNAME_LEN	((size_t)32)
+static int idt_create_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	struct i2c_client *cli = pdev->client;
+	char fname[CSRNAME_LEN];
+
+	/* Initialize basic value of CSR debugfs dentries */
+	pdev->csr_dir = NULL;
+	pdev->csr_file = NULL;
+
+	/* Return failure if root directory doesn't exist */
+	if (!csr_dbgdir) {
+		dev_dbg(dev, "No Debugfs root directory");
+		return -EINVAL;
+	}
+
+	/* Create Debugfs directory for CSR file */
+	snprintf(fname, CSRNAME_LEN, "%d-%04hx", cli->adapter->nr, cli->addr);
+	pdev->csr_dir = debugfs_create_dir(fname, csr_dbgdir);
+	if (IS_ERR_OR_NULL(pdev->csr_dir)) {
+		dev_err(dev, "Failed to create CSR node directory");
+		return -EINVAL;
+	}
+
+	/* Create Debugfs file for CSR read/write operations */
+	pdev->csr_file = debugfs_create_file(cli->name, 0600,
+		pdev->csr_dir, pdev, &csr_dbgfs_ops);
+	if (IS_ERR_OR_NULL(pdev->csr_file)) {
+		dev_err(dev, "Failed to create CSR dbgfs-node");
+		debugfs_remove_recursive(pdev->csr_dir);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "Debugfs-files created");
+
+	return 0;
+}
+
+/*
+ * idt_remove_dbgfs_files() - remove debugfs files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	/* Remove CSR directory and it sysfs-node */
+	debugfs_remove_recursive(pdev->csr_dir);
+
+	dev_dbg(&pdev->client->dev, "Debugfs-files removed");
+}
+
+/*
+ * idt_probe() - IDT 89HPESx driver probe() callback method
+ */
+static int idt_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Create driver data */
+	pdev = idt_create_pdev(client);
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	/* Set SMBus operations */
+	ret = idt_set_smbus_ops(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Check whether it is truly IDT 89HPESx device */
+	ret = idt_check_dev(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create sysfs files */
+	ret = idt_create_sysfs_files(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create debugfs files */
+	(void)idt_create_dbgfs_files(pdev);
+
+	dev_dbg(&client->dev, "IDT %s device probed", id->name);
+
+	return 0;
+
+err_free_pdev:
+	idt_free_pdev(pdev);
+
+	return ret;
+}
+
+/*
+ * idt_remove() - IDT 89HPESx driver remove() callback method
+ */
+static int idt_remove(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev = i2c_get_clientdata(client);
+
+	/* Remove debugfs files first */
+	idt_remove_dbgfs_files(pdev);
+
+	/* Remove sysfs files */
+	idt_remove_sysfs_files(pdev);
+
+	/* Discard driver data structure */
+	idt_free_pdev(pdev);
+
+	dev_dbg(&client->dev, "IDT 89HPESx device removed");
+
+	return 0;
+}
+
+/*
+ * idt_ids - supported IDT 89HPESx devices
+ */
+static const struct i2c_device_id idt_ids[] = {
+	{ "89hpes8nt2", 0 },
+	{ "89hpes12nt3", 0 },
+
+	{ "89hpes24nt6ag2", 0 },
+	{ "89hpes32nt8ag2", 0 },
+	{ "89hpes32nt8bg2", 0 },
+	{ "89hpes12nt12g2", 0 },
+	{ "89hpes16nt16g2", 0 },
+	{ "89hpes24nt24g2", 0 },
+	{ "89hpes32nt24ag2", 0 },
+	{ "89hpes32nt24bg2", 0 },
+
+	{ "89hpes12n3", 0 },
+	{ "89hpes12n3a", 0 },
+	{ "89hpes24n3", 0 },
+	{ "89hpes24n3a", 0 },
+
+	{ "89hpes32h8", 0 },
+	{ "89hpes32h8g2", 0 },
+	{ "89hpes48h12", 0 },
+	{ "89hpes48h12g2", 0 },
+	{ "89hpes48h12ag2", 0 },
+	{ "89hpes16h16", 0 },
+	{ "89hpes22h16", 0 },
+	{ "89hpes22h16g2", 0 },
+	{ "89hpes34h16", 0 },
+	{ "89hpes34h16g2", 0 },
+	{ "89hpes64h16", 0 },
+	{ "89hpes64h16g2", 0 },
+	{ "89hpes64h16ag2", 0 },
+
+	/* { "89hpes3t3", 0 }, // No SMBus-slave iface */
+	{ "89hpes12t3g2", 0 },
+	{ "89hpes24t3g2", 0 },
+	/* { "89hpes4t4", 0 }, // No SMBus-slave iface */
+	{ "89hpes16t4", 0 },
+	{ "89hpes4t4g2", 0 },
+	{ "89hpes10t4g2", 0 },
+	{ "89hpes16t4g2", 0 },
+	{ "89hpes16t4ag2", 0 },
+	{ "89hpes5t5", 0 },
+	{ "89hpes6t5", 0 },
+	{ "89hpes8t5", 0 },
+	{ "89hpes8t5a", 0 },
+	{ "89hpes24t6", 0 },
+	{ "89hpes6t6g2", 0 },
+	{ "89hpes24t6g2", 0 },
+	{ "89hpes16t7", 0 },
+	{ "89hpes32t8", 0 },
+	{ "89hpes32t8g2", 0 },
+	{ "89hpes48t12", 0 },
+	{ "89hpes48t12g2", 0 },
+	{ /* END OF LIST */ }
+};
+MODULE_DEVICE_TABLE(i2c, idt_ids);
+
+/*
+ * idt_driver - IDT 89HPESx driver structure
+ */
+static struct i2c_driver idt_driver = {
+	.driver = {
+		.name = IDT_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = idt_probe,
+	.remove = idt_remove,
+	.id_table = idt_ids,
+};
+
+/*
+ * idt_init() - IDT 89HPESx driver init() callback method
+ */
+static int __init idt_init(void)
+{
+	/* Create Debugfs directory first */
+	if (debugfs_initialized())
+		csr_dbgdir = debugfs_create_dir("idt_csr", NULL);
+
+	/* Add new i2c-device driver */
+	return i2c_add_driver(&idt_driver);
+}
+module_init(idt_init);
+
+/*
+ * idt_exit() - IDT 89HPESx driver exit() callback method
+ */
+static void __exit idt_exit(void)
+{
+	/* Discard debugfs directory and all files if any */
+	debugfs_remove_recursive(csr_dbgdir);
+
+	/* Unregister i2c-device driver */
+	i2c_del_driver(&idt_driver);
+}
+module_exit(idt_exit);
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v3 2/2] eeprom: Add IDT 89HPESx driver dts-binding file
  2016-11-29 22:27   ` [PATCH v3 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2016-11-29 22:27     ` [PATCH v3 1/2] " Serge Semin
@ 2016-11-29 22:27     ` Serge Semin
  2016-12-13 14:22     ` [PATCH v4 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2 siblings, 0 replies; 34+ messages in thread
From: Serge Semin @ 2016-11-29 22:27 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

IDT 89HPESx PCIe-switches exposes SMBus interface to have an access to
the device CSRs and EEPROM. So to properly utilize the interface
functionality, developer should declare a valid dts-file node, which
would refer to the corresponding 89HPESx device.

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
 .../devicetree/bindings/misc/idt_89hpesx.txt       | 41 ++++++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt

diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
index 0000000..469cc93
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
@@ -0,0 +1,41 @@
+EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
+
+Required properties:
+  - compatible : should be "<manufacturer>,<type>"
+		 Basically there is only one manufacturer: idt, but some
+		 compatible devices may be produced in future. Following devices
+		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
+		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
+		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
+		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
+		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
+		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
+		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
+		 89hpes64h16ag2;
+		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
+		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
+		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
+		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
+		 89hpes48t12, 89hpes48t12g2.
+		 Current implementation of the driver doesn't have any device-
+		 specific functionalities. But since each of them differs
+		 by registers mapping, CSRs read/write restrictions can be
+		 added in future.
+  - reg :	 I2C address of the IDT 89HPES device.
+
+Optional properties:
+  - read-only :	 Parameterless property disables writes to the EEPROM
+  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
+		 (default value is 4096 bytes if option isn't specified)
+  - idt,eeaddr : Custom address of EEPROM device
+		 (If not specified IDT 89HPESx device will try to communicate
+		  with EEPROM sited by default address - 0x50)
+
+Example:
+	idt_pcie_sw@60 {
+		compatible = "idt,89hpes12nt3";
+		reg = <0x60>;
+		read-only;
+		idt,eesize = <65536>;
+		idt,eeaddr = <0x50>;
+	};
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-11-28 22:38   ` [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
  2016-11-29 19:34     ` Greg KH
@ 2016-12-05 14:46     ` Rob Herring
  2016-12-05 15:25       ` Serge Semin
  1 sibling, 1 reply; 34+ messages in thread
From: Rob Herring @ 2016-12-05 14:46 UTC (permalink / raw)
  To: Serge Semin
  Cc: gregkh, srinivas.kandagatla, andrew, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Tue, Nov 29, 2016 at 01:38:21AM +0300, Serge Semin wrote:
> See cover-letter for changelog
> 
> Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
> 
> ---
>  .../devicetree/bindings/misc/idt_89hpesx.txt       | 41 ++++++++++++++++++++++

There's not a better location for this? I can't tell because you don't 
describe what the device is.

>  1 file changed, 41 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> 
> diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> index 0000000..469cc93
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> @@ -0,0 +1,41 @@
> +EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
> +
> +Required properties:
> +  - compatible : should be "<manufacturer>,<type>"
> +		 Basically there is only one manufacturer: idt, but some
> +		 compatible devices may be produced in future. Following devices
> +		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
> +		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
> +		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
> +		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
> +		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
> +		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
> +		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
> +		 89hpes64h16ag2;
> +		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
> +		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
> +		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
> +		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
> +		 89hpes48t12, 89hpes48t12g2.
> +		 Current implementation of the driver doesn't have any device-

Driver capabilties are irrelevant to bindings.

> +		 specific functionalities. But since each of them differs
> +		 by registers mapping, CSRs read/write restrictions can be
> +		 added in future.
> +  - reg :	 I2C address of the IDT 89HPES device.
> +
> +Optional properties:
> +  - read-only :	 Parameterless property disables writes to the EEPROM
> +  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
> +		 (default value is 4096 bytes if option isn't specified)
> +  - idt,eeaddr : Custom address of EEPROM device
> +		 (If not specified IDT 89HPESx device will try to communicate
> +		  with EEPROM sited by default address - 0x50)

Don't we already have standard EEPROM properties that could be used 
here?

> +
> +Example:
> +	idt_pcie_sw@60 {

Don't use '_'.

> +		compatible = "idt,89hpes12nt3";
> +		reg = <0x60>;
> +		read-only;
> +		idt,eesize = <65536>;
> +		idt,eeaddr = <0x50>;
> +	};
> -- 
> 2.6.6
> 

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-12-05 14:46     ` Rob Herring
@ 2016-12-05 15:25       ` Serge Semin
  2016-12-05 17:27         ` Rob Herring
  0 siblings, 1 reply; 34+ messages in thread
From: Serge Semin @ 2016-12-05 15:25 UTC (permalink / raw)
  To: Rob Herring
  Cc: gregkh, srinivas.kandagatla, andrew, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Mon, Dec 05, 2016 at 08:46:21AM -0600, Rob Herring <robh@kernel.org> wrote:
> On Tue, Nov 29, 2016 at 01:38:21AM +0300, Serge Semin wrote:
> > See cover-letter for changelog
> > 
> > Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
> > 
> > ---
> >  .../devicetree/bindings/misc/idt_89hpesx.txt       | 41 ++++++++++++++++++++++
> 
> There's not a better location for this? I can't tell because you don't 
> describe what the device is.
> 

The device is PCIe-switch EEPROM driver with additional debug-interface to
access the switch CSRs. EEPROM is accesses via a separate i2c-slave
interface of the switch.

There might be another place to put the binding file in. There is a special
location for EEPROM drivers bindings - Documentation/devicetree/bindings/eeprom/ .
But as far as I understood from the files put in there, it's intended for
pure EEPROM drivers only. On the other hand the directory I've chosen:
Documentation/devicetree/bindings/misc/
mostly intended for some unusual devices. My device isn't usual, since it
has CSRs debug-interface as well. Additionally I've found
eeprom-93xx46.txt binding file there, which describes EEPROM bindings.

Anyway if you find the file should be placed in 
Documentation/devicetree/bindings/eeprom/ instead, I'll move it, it's not
that a big problem.

> >  1 file changed, 41 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> > 
> > diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> > index 0000000..469cc93
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> > @@ -0,0 +1,41 @@
> > +EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
> > +
> > +Required properties:
> > +  - compatible : should be "<manufacturer>,<type>"
> > +		 Basically there is only one manufacturer: idt, but some
> > +		 compatible devices may be produced in future. Following devices
> > +		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
> > +		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
> > +		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
> > +		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
> > +		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
> > +		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
> > +		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
> > +		 89hpes64h16ag2;
> > +		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
> > +		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
> > +		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
> > +		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
> > +		 89hpes48t12, 89hpes48t12g2.
> > +		 Current implementation of the driver doesn't have any device-
> 
> Driver capabilties are irrelevant to bindings.
> 

Why? I've told in the comment, that the devices actually differ by the CSRs
map. Even though it's not reflected in the code at the moment, the CSRs
read/write restrictions can be added by some concerned programmer in
future. But If I left something like "compatible : idt,89hpesx" device
only, it will be problematic to add that functionality.

Howbeit If you think it's not necessary and "compatible = idt,89hpesx" is
ok, it's perfectly fine for me to make it this way. The property will be
even simpler, than current approach.

> > +		 specific functionalities. But since each of them differs
> > +		 by registers mapping, CSRs read/write restrictions can be
> > +		 added in future.
> > +  - reg :	 I2C address of the IDT 89HPES device.
> > +
> > +Optional properties:
> > +  - read-only :	 Parameterless property disables writes to the EEPROM
> > +  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
> > +		 (default value is 4096 bytes if option isn't specified)
> > +  - idt,eeaddr : Custom address of EEPROM device
> > +		 (If not specified IDT 89HPESx device will try to communicate
> > +		  with EEPROM sited by default address - 0x50)
> 
> Don't we already have standard EEPROM properties that could be used 
> here?
> 

If we do, just tell me which one. There are standard options:
"compatible, reg, pagesize, read-only". There isn't any connected with
EEPROM actual size.
Why so? Because standard EEPROM-drivers determine the device size from the
compatible-string name. Such approach won't work in this case, because
PCIe-switch and it EEPROM are actually two different devices. Look at the
chain of the usual platform board design:
Host <--- i2c ----> i2c-slave iface |PCIe-switch| i2c-master iface <--- i2c ---> EEPROM

As you cas see the Host reaches EEPROM through the set of PCIe-switch
i2c-interfaces. In order to properly get data from it my driver needs actual
EEPROM size and it address in the i2c-master bus of the PCIe-switch, in
addition to the standard reg-field, which is address of PCIe-switch i2c-slave
interface and read-only parameter if EEPROM-device has got WP pin asserted.

> > +
> > +Example:
> > +	idt_pcie_sw@60 {
> 
> Don't use '_'.
> 

Ok, I won't.

> > +		compatible = "idt,89hpes12nt3";
> > +		reg = <0x60>;
> > +		read-only;
> > +		idt,eesize = <65536>;
> > +		idt,eeaddr = <0x50>;
> > +	};
> > -- 
> > 2.6.6
> > 

Waiting for the respond.
Thanks
-Sergey

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-12-05 15:25       ` Serge Semin
@ 2016-12-05 17:27         ` Rob Herring
  2016-12-05 19:04           ` Serge Semin
  0 siblings, 1 reply; 34+ messages in thread
From: Rob Herring @ 2016-12-05 17:27 UTC (permalink / raw)
  To: Serge Semin
  Cc: Greg Kroah-Hartman, Srinivas Kandagatla, Andrew Lunn,
	Mark Rutland, Sergey.Semin, linux-kernel, devicetree

On Mon, Dec 5, 2016 at 9:25 AM, Serge Semin <fancer.lancer@gmail.com> wrote:
> On Mon, Dec 05, 2016 at 08:46:21AM -0600, Rob Herring <robh@kernel.org> wrote:
>> On Tue, Nov 29, 2016 at 01:38:21AM +0300, Serge Semin wrote:
>> > See cover-letter for changelog
>> >
>> > Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
>> >
>> > ---
>> >  .../devicetree/bindings/misc/idt_89hpesx.txt       | 41 ++++++++++++++++++++++
>>
>> There's not a better location for this? I can't tell because you don't
>> describe what the device is.
>>
>
> The device is PCIe-switch EEPROM driver with additional debug-interface to
> access the switch CSRs. EEPROM is accesses via a separate i2c-slave
> interface of the switch.
>
> There might be another place to put the binding file in. There is a special
> location for EEPROM drivers bindings - Documentation/devicetree/bindings/eeprom/ .
> But as far as I understood from the files put in there, it's intended for
> pure EEPROM drivers only. On the other hand the directory I've chosen:
> Documentation/devicetree/bindings/misc/
> mostly intended for some unusual devices. My device isn't usual, since it
> has CSRs debug-interface as well. Additionally I've found
> eeprom-93xx46.txt binding file there, which describes EEPROM bindings.
>
> Anyway if you find the file should be placed in
> Documentation/devicetree/bindings/eeprom/ instead, I'll move it, it's not
> that a big problem.
>
>> >  1 file changed, 41 insertions(+)
>> >  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
>> >
>> > diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
>> > index 0000000..469cc93
>> > --- /dev/null
>> > +++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
>> > @@ -0,0 +1,41 @@
>> > +EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
>> > +
>> > +Required properties:
>> > +  - compatible : should be "<manufacturer>,<type>"
>> > +            Basically there is only one manufacturer: idt, but some
>> > +            compatible devices may be produced in future. Following devices
>> > +            are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
>> > +            89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
>> > +            89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
>> > +            89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
>> > +            89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
>> > +            89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
>> > +            89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
>> > +            89hpes64h16ag2;
>> > +            89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
>> > +            89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
>> > +            89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
>> > +            89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
>> > +            89hpes48t12, 89hpes48t12g2.
>> > +            Current implementation of the driver doesn't have any device-
>>
>> Driver capabilties are irrelevant to bindings.
>>
>
> Why? I've told in the comment, that the devices actually differ by the CSRs
> map. Even though it's not reflected in the code at the moment, the CSRs
> read/write restrictions can be added by some concerned programmer in
> future. But If I left something like "compatible : idt,89hpesx" device
> only, it will be problematic to add that functionality.

Bindings describe the h/w, not what the Linux, FreeBSD, etc. driver
does. You don't want to be changing the binding doc when the driver
changes.

> Howbeit If you think it's not necessary and "compatible = idt,89hpesx" is
> ok, it's perfectly fine for me to make it this way. The property will be
> even simpler, than current approach.

NO! That's not at all what I'm suggesting. Specific compatible strings
are the right way to go for the reasons you give. You just don't need
to state why here (because it is true for all bindings).

>> > +            specific functionalities. But since each of them differs
>> > +            by registers mapping, CSRs read/write restrictions can be
>> > +            added in future.
>> > +  - reg :   I2C address of the IDT 89HPES device.
>> > +
>> > +Optional properties:
>> > +  - read-only :     Parameterless property disables writes to the EEPROM
>> > +  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
>> > +            (default value is 4096 bytes if option isn't specified)
>> > +  - idt,eeaddr : Custom address of EEPROM device
>> > +            (If not specified IDT 89HPESx device will try to communicate
>> > +             with EEPROM sited by default address - 0x50)
>>
>> Don't we already have standard EEPROM properties that could be used
>> here?
>>
>
> If we do, just tell me which one. There are standard options:

You can grep thru bindings as easily as I can. I can't do that for
everyone's binding.

> "compatible, reg, pagesize, read-only". There isn't any connected with
> EEPROM actual size.
> Why so? Because standard EEPROM-drivers determine the device size from the
> compatible-string name. Such approach won't work in this case, because
> PCIe-switch and it EEPROM are actually two different devices. Look at the
> chain of the usual platform board design:
> Host <--- i2c ----> i2c-slave iface |PCIe-switch| i2c-master iface <--- i2c ---> EEPROM
>
> As you cas see the Host reaches EEPROM through the set of PCIe-switch
> i2c-interfaces. In order to properly get data from it my driver needs actual
> EEPROM size and it address in the i2c-master bus of the PCIe-switch, in
> addition to the standard reg-field, which is address of PCIe-switch i2c-slave
> interface and read-only parameter if EEPROM-device has got WP pin asserted.

Ah, this needs to be much different than I thought. You need to model
(i.e. use the same binding) the EEPROM node just like it was directly
attached to the host. So this means you need the 2nd i2c bus modeled
which means you need the PCIe switch modeled. A rough outline of the
nodes would look like this:

host-i2c: i2c {
  compatible ="host-i2c"
};

pcie {
    pcie-switch {
        i2c-bus = <&host-i2c>;
        i2c-bus {
            eeprom@50 {
            };
        };
    };
};

So this models the PCIe switch as a PCIe device, it has a phandle back
to it's controller since it's not a child of the i2c controller. Then
the devices on switches i2c bus are modeled as children of the switch.

Alternatively, it could be described all as children of host-i2c node.
It's common for i2c devices to have downstream i2c buses. I2C muxes
are one example and there are bindings defined for all this. There's
also chips like mpu-6050 that have slave buses.

Rob

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-12-05 17:27         ` Rob Herring
@ 2016-12-05 19:04           ` Serge Semin
  2016-12-09  1:57             ` Serge Semin
  2016-12-12 23:04             ` Rob Herring
  0 siblings, 2 replies; 34+ messages in thread
From: Serge Semin @ 2016-12-05 19:04 UTC (permalink / raw)
  To: Rob Herring
  Cc: Greg Kroah-Hartman, Srinivas Kandagatla, Andrew Lunn,
	Mark Rutland, Sergey.Semin, linux-kernel, devicetree

On Mon, Dec 05, 2016 at 11:27:07AM -0600, Rob Herring <robh@kernel.org> wrote:
> On Mon, Dec 5, 2016 at 9:25 AM, Serge Semin <fancer.lancer@gmail.com> wrote:
> > On Mon, Dec 05, 2016 at 08:46:21AM -0600, Rob Herring <robh@kernel.org> wrote:
> >> On Tue, Nov 29, 2016 at 01:38:21AM +0300, Serge Semin wrote:
> >> > See cover-letter for changelog
> >> >
> >> > Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
> >> >
> >> > ---
> >> >  .../devicetree/bindings/misc/idt_89hpesx.txt       | 41 ++++++++++++++++++++++
> >>
> >> There's not a better location for this? I can't tell because you don't
> >> describe what the device is.
> >>
> >
> > The device is PCIe-switch EEPROM driver with additional debug-interface to
> > access the switch CSRs. EEPROM is accesses via a separate i2c-slave
> > interface of the switch.
> >
> > There might be another place to put the binding file in. There is a special
> > location for EEPROM drivers bindings - Documentation/devicetree/bindings/eeprom/ .
> > But as far as I understood from the files put in there, it's intended for
> > pure EEPROM drivers only. On the other hand the directory I've chosen:
> > Documentation/devicetree/bindings/misc/
> > mostly intended for some unusual devices. My device isn't usual, since it
> > has CSRs debug-interface as well. Additionally I've found
> > eeprom-93xx46.txt binding file there, which describes EEPROM bindings.
> >
> > Anyway if you find the file should be placed in
> > Documentation/devicetree/bindings/eeprom/ instead, I'll move it, it's not
> > that a big problem.
> >

What about this comment? Shall the file be left at the path I placed it?

> >> >  1 file changed, 41 insertions(+)
> >> >  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> >> >
> >> > diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> >> > index 0000000..469cc93
> >> > --- /dev/null
> >> > +++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> >> > @@ -0,0 +1,41 @@
> >> > +EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
> >> > +
> >> > +Required properties:
> >> > +  - compatible : should be "<manufacturer>,<type>"
> >> > +            Basically there is only one manufacturer: idt, but some
> >> > +            compatible devices may be produced in future. Following devices
> >> > +            are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
> >> > +            89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
> >> > +            89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
> >> > +            89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
> >> > +            89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
> >> > +            89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
> >> > +            89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
> >> > +            89hpes64h16ag2;
> >> > +            89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
> >> > +            89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
> >> > +            89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
> >> > +            89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
> >> > +            89hpes48t12, 89hpes48t12g2.
> >> > +            Current implementation of the driver doesn't have any device-
> >>
> >> Driver capabilties are irrelevant to bindings.
> >>
> >
> > Why? I've told in the comment, that the devices actually differ by the CSRs
> > map. Even though it's not reflected in the code at the moment, the CSRs
> > read/write restrictions can be added by some concerned programmer in
> > future. But If I left something like "compatible : idt,89hpesx" device
> > only, it will be problematic to add that functionality.
> 
> Bindings describe the h/w, not what the Linux, FreeBSD, etc. driver
> does. You don't want to be changing the binding doc when the driver
> changes.
> 
> > Howbeit If you think it's not necessary and "compatible = idt,89hpesx" is
> > ok, it's perfectly fine for me to make it this way. The property will be
> > even simpler, than current approach.
> 
> NO! That's not at all what I'm suggesting. Specific compatible strings
> are the right way to go for the reasons you give. You just don't need
> to state why here (because it is true for all bindings).
> 

Oh, I just misunderstood what you said. I'll discard the comment.

> >> > +            specific functionalities. But since each of them differs
> >> > +            by registers mapping, CSRs read/write restrictions can be
> >> > +            added in future.
> >> > +  - reg :   I2C address of the IDT 89HPES device.
> >> > +
> >> > +Optional properties:
> >> > +  - read-only :     Parameterless property disables writes to the EEPROM
> >> > +  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
> >> > +            (default value is 4096 bytes if option isn't specified)
> >> > +  - idt,eeaddr : Custom address of EEPROM device
> >> > +            (If not specified IDT 89HPESx device will try to communicate
> >> > +             with EEPROM sited by default address - 0x50)
> >>
> >> Don't we already have standard EEPROM properties that could be used
> >> here?
> >>
> >
> > If we do, just tell me which one. There are standard options:
> 
> You can grep thru bindings as easily as I can. I can't do that for
> everyone's binding.
> 

It won't be necessary due to the next comment.

> > "compatible, reg, pagesize, read-only". There isn't any connected with
> > EEPROM actual size.
> > Why so? Because standard EEPROM-drivers determine the device size from the
> > compatible-string name. Such approach won't work in this case, because
> > PCIe-switch and it EEPROM are actually two different devices. Look at the
> > chain of the usual platform board design:
> > Host <--- i2c ----> i2c-slave iface |PCIe-switch| i2c-master iface <--- i2c ---> EEPROM
> >
> > As you cas see the Host reaches EEPROM through the set of PCIe-switch
> > i2c-interfaces. In order to properly get data from it my driver needs actual
> > EEPROM size and it address in the i2c-master bus of the PCIe-switch, in
> > addition to the standard reg-field, which is address of PCIe-switch i2c-slave
> > interface and read-only parameter if EEPROM-device has got WP pin asserted.
> 
> Ah, this needs to be much different than I thought. You need to model
> (i.e. use the same binding) the EEPROM node just like it was directly
> attached to the host. So this means you need the 2nd i2c bus modeled
> which means you need the PCIe switch modeled. A rough outline of the
> nodes would look like this:
> 
> host-i2c: i2c {
>   compatible ="host-i2c"
> };
> 
> pcie {
>     pcie-switch {
>         i2c-bus = <&host-i2c>;
>         i2c-bus {
>             eeprom@50 {
>             };
>         };
>     };
> };
> 
> So this models the PCIe switch as a PCIe device, it has a phandle back
> to it's controller since it's not a child of the i2c controller. Then
> the devices on switches i2c bus are modeled as children of the switch.
> 
> Alternatively, it could be described all as children of host-i2c node.
> It's common for i2c devices to have downstream i2c buses. I2C muxes
> are one example and there are bindings defined for all this. There's
> also chips like mpu-6050 that have slave buses.
> 
> Rob

I think I understand what you says. However let me just bring some details
to make things clear.

First of all the driver doesn't do any PCI-Express-related work. The device
!IDT PCI Express switch! just has two additional i2c interfaces: i2c-slave
and i2c-master. As it is obvious from the bus-names i2c-slave is the interface,
where IDT PCIe-switch device is actually slave. This interface can be reached
from the host by ordinary i2c buses. i2c-master interface is connected to an
i2c-bus, where IDT PCIe-switch is single master. This bus can have just one
EEPROM device to store some initialization data. Host can send some specific
smbus-packets to i2c-slave interface of IDT PCIe-switch in order to
preinitialize EEPROM data, connected to i2c-master interface of the device.

Additionally IDT PCIe-switch handles some special smbus packets coming to it
i2c-slave interface to read/write its internal CSR. This interface can be
used to debug the device, when there are problems with it usual PCI Express
related functioning.

So to speak, it wouldn't be good to have PCIe-switch declared in dts as a
PCI-device, since PCI-bus is actually dynamically populated by PCI-core
subsystem.

According to what you said and the device/driver design I described, the
following bindings can be suggested:

i2c0: i2c@FFFF0000 {
	compatible = "vendor,i2c-adapter";
	#address-cells = <1>;
	#size-cells = <0>;

	idt_i2c_iface: idt@60 {
		compatible = "idt,89hpes32nt8ag2";
		reg = <0x60>;
		#address-cells = <1>;
		#size-cells = <0>;

		eeprom@51 {
			compatible = "at,24c64";
			reg = <0x51>;
			read-only;
		};
	};
};

Suppose there is some host-i2c adapter like "vendor,i2c-adapter" and
i2c-slave interface of IDT PCIe-switch is connected to it. In this way
i2c-slave interface will be visible like ordinary i2c-device with just
one subnode. This subnode explains the actual EEPROM connected to
IDT PCIe-switch i2c-master interface.

Does it look acceptable? It seems like your last suggestion. Is it?

Thanks,
-Sergey

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-12-05 19:04           ` Serge Semin
@ 2016-12-09  1:57             ` Serge Semin
  2016-12-12 23:04             ` Rob Herring
  1 sibling, 0 replies; 34+ messages in thread
From: Serge Semin @ 2016-12-09  1:57 UTC (permalink / raw)
  To: Rob Herring
  Cc: Greg Kroah-Hartman, Srinivas Kandagatla, Andrew Lunn,
	Mark Rutland, Sergey.Semin, linux-kernel, devicetree

Rob,
Could you please respond on these comments? I've got some free time, so I wanna
rewrite the code until I've not got busy again.

Regards,
-Sergey

On Mon, Dec 05, 2016 at 10:04:56PM +0300, Serge Semin <fancer.lancer@gmail.com> wrote:
> On Mon, Dec 05, 2016 at 11:27:07AM -0600, Rob Herring <robh@kernel.org> wrote:
> > On Mon, Dec 5, 2016 at 9:25 AM, Serge Semin <fancer.lancer@gmail.com> wrote:
> > > On Mon, Dec 05, 2016 at 08:46:21AM -0600, Rob Herring <robh@kernel.org> wrote:
> > >> On Tue, Nov 29, 2016 at 01:38:21AM +0300, Serge Semin wrote:
> > >> > See cover-letter for changelog
> > >> >
> > >> > Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
> > >> >
> > >> > ---
> > >> >  .../devicetree/bindings/misc/idt_89hpesx.txt       | 41 ++++++++++++++++++++++
> > >>
> > >> There's not a better location for this? I can't tell because you don't
> > >> describe what the device is.
> > >>
> > >
> > > The device is PCIe-switch EEPROM driver with additional debug-interface to
> > > access the switch CSRs. EEPROM is accesses via a separate i2c-slave
> > > interface of the switch.
> > >
> > > There might be another place to put the binding file in. There is a special
> > > location for EEPROM drivers bindings - Documentation/devicetree/bindings/eeprom/ .
> > > But as far as I understood from the files put in there, it's intended for
> > > pure EEPROM drivers only. On the other hand the directory I've chosen:
> > > Documentation/devicetree/bindings/misc/
> > > mostly intended for some unusual devices. My device isn't usual, since it
> > > has CSRs debug-interface as well. Additionally I've found
> > > eeprom-93xx46.txt binding file there, which describes EEPROM bindings.
> > >
> > > Anyway if you find the file should be placed in
> > > Documentation/devicetree/bindings/eeprom/ instead, I'll move it, it's not
> > > that a big problem.
> > >
> 
> What about this comment? Shall the file be left at the path I placed it?
> 
> > >> >  1 file changed, 41 insertions(+)
> > >> >  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> > >> >
> > >> > diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> > >> > index 0000000..469cc93
> > >> > --- /dev/null
> > >> > +++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> > >> > @@ -0,0 +1,41 @@
> > >> > +EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
> > >> > +
> > >> > +Required properties:
> > >> > +  - compatible : should be "<manufacturer>,<type>"
> > >> > +            Basically there is only one manufacturer: idt, but some
> > >> > +            compatible devices may be produced in future. Following devices
> > >> > +            are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
> > >> > +            89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
> > >> > +            89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
> > >> > +            89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
> > >> > +            89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
> > >> > +            89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
> > >> > +            89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
> > >> > +            89hpes64h16ag2;
> > >> > +            89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
> > >> > +            89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
> > >> > +            89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
> > >> > +            89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
> > >> > +            89hpes48t12, 89hpes48t12g2.
> > >> > +            Current implementation of the driver doesn't have any device-
> > >>
> > >> Driver capabilties are irrelevant to bindings.
> > >>
> > >
> > > Why? I've told in the comment, that the devices actually differ by the CSRs
> > > map. Even though it's not reflected in the code at the moment, the CSRs
> > > read/write restrictions can be added by some concerned programmer in
> > > future. But If I left something like "compatible : idt,89hpesx" device
> > > only, it will be problematic to add that functionality.
> > 
> > Bindings describe the h/w, not what the Linux, FreeBSD, etc. driver
> > does. You don't want to be changing the binding doc when the driver
> > changes.
> > 
> > > Howbeit If you think it's not necessary and "compatible = idt,89hpesx" is
> > > ok, it's perfectly fine for me to make it this way. The property will be
> > > even simpler, than current approach.
> > 
> > NO! That's not at all what I'm suggesting. Specific compatible strings
> > are the right way to go for the reasons you give. You just don't need
> > to state why here (because it is true for all bindings).
> > 
> 
> Oh, I just misunderstood what you said. I'll discard the comment.
> 
> > >> > +            specific functionalities. But since each of them differs
> > >> > +            by registers mapping, CSRs read/write restrictions can be
> > >> > +            added in future.
> > >> > +  - reg :   I2C address of the IDT 89HPES device.
> > >> > +
> > >> > +Optional properties:
> > >> > +  - read-only :     Parameterless property disables writes to the EEPROM
> > >> > +  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
> > >> > +            (default value is 4096 bytes if option isn't specified)
> > >> > +  - idt,eeaddr : Custom address of EEPROM device
> > >> > +            (If not specified IDT 89HPESx device will try to communicate
> > >> > +             with EEPROM sited by default address - 0x50)
> > >>
> > >> Don't we already have standard EEPROM properties that could be used
> > >> here?
> > >>
> > >
> > > If we do, just tell me which one. There are standard options:
> > 
> > You can grep thru bindings as easily as I can. I can't do that for
> > everyone's binding.
> > 
> 
> It won't be necessary due to the next comment.
> 
> > > "compatible, reg, pagesize, read-only". There isn't any connected with
> > > EEPROM actual size.
> > > Why so? Because standard EEPROM-drivers determine the device size from the
> > > compatible-string name. Such approach won't work in this case, because
> > > PCIe-switch and it EEPROM are actually two different devices. Look at the
> > > chain of the usual platform board design:
> > > Host <--- i2c ----> i2c-slave iface |PCIe-switch| i2c-master iface <--- i2c ---> EEPROM
> > >
> > > As you cas see the Host reaches EEPROM through the set of PCIe-switch
> > > i2c-interfaces. In order to properly get data from it my driver needs actual
> > > EEPROM size and it address in the i2c-master bus of the PCIe-switch, in
> > > addition to the standard reg-field, which is address of PCIe-switch i2c-slave
> > > interface and read-only parameter if EEPROM-device has got WP pin asserted.
> > 
> > Ah, this needs to be much different than I thought. You need to model
> > (i.e. use the same binding) the EEPROM node just like it was directly
> > attached to the host. So this means you need the 2nd i2c bus modeled
> > which means you need the PCIe switch modeled. A rough outline of the
> > nodes would look like this:
> > 
> > host-i2c: i2c {
> >   compatible ="host-i2c"
> > };
> > 
> > pcie {
> >     pcie-switch {
> >         i2c-bus = <&host-i2c>;
> >         i2c-bus {
> >             eeprom@50 {
> >             };
> >         };
> >     };
> > };
> > 
> > So this models the PCIe switch as a PCIe device, it has a phandle back
> > to it's controller since it's not a child of the i2c controller. Then
> > the devices on switches i2c bus are modeled as children of the switch.
> > 
> > Alternatively, it could be described all as children of host-i2c node.
> > It's common for i2c devices to have downstream i2c buses. I2C muxes
> > are one example and there are bindings defined for all this. There's
> > also chips like mpu-6050 that have slave buses.
> > 
> > Rob
> 
> I think I understand what you says. However let me just bring some details
> to make things clear.
> 
> First of all the driver doesn't do any PCI-Express-related work. The device
> !IDT PCI Express switch! just has two additional i2c interfaces: i2c-slave
> and i2c-master. As it is obvious from the bus-names i2c-slave is the interface,
> where IDT PCIe-switch device is actually slave. This interface can be reached
> from the host by ordinary i2c buses. i2c-master interface is connected to an
> i2c-bus, where IDT PCIe-switch is single master. This bus can have just one
> EEPROM device to store some initialization data. Host can send some specific
> smbus-packets to i2c-slave interface of IDT PCIe-switch in order to
> preinitialize EEPROM data, connected to i2c-master interface of the device.
> 
> Additionally IDT PCIe-switch handles some special smbus packets coming to it
> i2c-slave interface to read/write its internal CSR. This interface can be
> used to debug the device, when there are problems with it usual PCI Express
> related functioning.
> 
> So to speak, it wouldn't be good to have PCIe-switch declared in dts as a
> PCI-device, since PCI-bus is actually dynamically populated by PCI-core
> subsystem.
> 
> According to what you said and the device/driver design I described, the
> following bindings can be suggested:
> 
> i2c0: i2c@FFFF0000 {
> 	compatible = "vendor,i2c-adapter";
> 	#address-cells = <1>;
> 	#size-cells = <0>;
> 
> 	idt_i2c_iface: idt@60 {
> 		compatible = "idt,89hpes32nt8ag2";
> 		reg = <0x60>;
> 		#address-cells = <1>;
> 		#size-cells = <0>;
> 
> 		eeprom@51 {
> 			compatible = "at,24c64";
> 			reg = <0x51>;
> 			read-only;
> 		};
> 	};
> };
> 
> Suppose there is some host-i2c adapter like "vendor,i2c-adapter" and
> i2c-slave interface of IDT PCIe-switch is connected to it. In this way
> i2c-slave interface will be visible like ordinary i2c-device with just
> one subnode. This subnode explains the actual EEPROM connected to
> IDT PCIe-switch i2c-master interface.
> 
> Does it look acceptable? It seems like your last suggestion. Is it?
> 
> Thanks,
> -Sergey
> 

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-12-05 19:04           ` Serge Semin
  2016-12-09  1:57             ` Serge Semin
@ 2016-12-12 23:04             ` Rob Herring
  2016-12-13 14:08               ` Serge Semin
  1 sibling, 1 reply; 34+ messages in thread
From: Rob Herring @ 2016-12-12 23:04 UTC (permalink / raw)
  To: Serge Semin
  Cc: Greg Kroah-Hartman, Srinivas Kandagatla, Andrew Lunn,
	Mark Rutland, Sergey.Semin, linux-kernel, devicetree

On Mon, Dec 5, 2016 at 1:04 PM, Serge Semin <fancer.lancer@gmail.com> wrote:
> On Mon, Dec 05, 2016 at 11:27:07AM -0600, Rob Herring <robh@kernel.org> wrote:
>> On Mon, Dec 5, 2016 at 9:25 AM, Serge Semin <fancer.lancer@gmail.com> wrote:
>> > On Mon, Dec 05, 2016 at 08:46:21AM -0600, Rob Herring <robh@kernel.org> wrote:
>> >> On Tue, Nov 29, 2016 at 01:38:21AM +0300, Serge Semin wrote:
>> >> > See cover-letter for changelog
>> >> >
>> >> > Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
>> >> >
>> >> > ---
>> >> >  .../devicetree/bindings/misc/idt_89hpesx.txt       | 41 ++++++++++++++++++++++
>> >>
>> >> There's not a better location for this? I can't tell because you don't
>> >> describe what the device is.
>> >>
>> >
>> > The device is PCIe-switch EEPROM driver with additional debug-interface to
>> > access the switch CSRs. EEPROM is accesses via a separate i2c-slave
>> > interface of the switch.
>> >
>> > There might be another place to put the binding file in. There is a special
>> > location for EEPROM drivers bindings - Documentation/devicetree/bindings/eeprom/ .
>> > But as far as I understood from the files put in there, it's intended for
>> > pure EEPROM drivers only. On the other hand the directory I've chosen:
>> > Documentation/devicetree/bindings/misc/
>> > mostly intended for some unusual devices. My device isn't usual, since it
>> > has CSRs debug-interface as well. Additionally I've found
>> > eeprom-93xx46.txt binding file there, which describes EEPROM bindings.
>> >
>> > Anyway if you find the file should be placed in
>> > Documentation/devicetree/bindings/eeprom/ instead, I'll move it, it's not
>> > that a big problem.
>> >
>
> What about this comment? Shall the file be left at the path I placed it?
>
>> >> >  1 file changed, 41 insertions(+)
>> >> >  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
>> >> >
>> >> > diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
>> >> > index 0000000..469cc93
>> >> > --- /dev/null
>> >> > +++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
>> >> > @@ -0,0 +1,41 @@
>> >> > +EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
>> >> > +
>> >> > +Required properties:
>> >> > +  - compatible : should be "<manufacturer>,<type>"
>> >> > +            Basically there is only one manufacturer: idt, but some
>> >> > +            compatible devices may be produced in future. Following devices
>> >> > +            are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
>> >> > +            89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
>> >> > +            89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
>> >> > +            89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
>> >> > +            89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
>> >> > +            89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
>> >> > +            89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
>> >> > +            89hpes64h16ag2;
>> >> > +            89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
>> >> > +            89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
>> >> > +            89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
>> >> > +            89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
>> >> > +            89hpes48t12, 89hpes48t12g2.
>> >> > +            Current implementation of the driver doesn't have any device-
>> >>
>> >> Driver capabilties are irrelevant to bindings.
>> >>
>> >
>> > Why? I've told in the comment, that the devices actually differ by the CSRs
>> > map. Even though it's not reflected in the code at the moment, the CSRs
>> > read/write restrictions can be added by some concerned programmer in
>> > future. But If I left something like "compatible : idt,89hpesx" device
>> > only, it will be problematic to add that functionality.
>>
>> Bindings describe the h/w, not what the Linux, FreeBSD, etc. driver
>> does. You don't want to be changing the binding doc when the driver
>> changes.
>>
>> > Howbeit If you think it's not necessary and "compatible = idt,89hpesx" is
>> > ok, it's perfectly fine for me to make it this way. The property will be
>> > even simpler, than current approach.
>>
>> NO! That's not at all what I'm suggesting. Specific compatible strings
>> are the right way to go for the reasons you give. You just don't need
>> to state why here (because it is true for all bindings).
>>
>
> Oh, I just misunderstood what you said. I'll discard the comment.
>
>> >> > +            specific functionalities. But since each of them differs
>> >> > +            by registers mapping, CSRs read/write restrictions can be
>> >> > +            added in future.
>> >> > +  - reg :   I2C address of the IDT 89HPES device.
>> >> > +
>> >> > +Optional properties:
>> >> > +  - read-only :     Parameterless property disables writes to the EEPROM
>> >> > +  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
>> >> > +            (default value is 4096 bytes if option isn't specified)
>> >> > +  - idt,eeaddr : Custom address of EEPROM device
>> >> > +            (If not specified IDT 89HPESx device will try to communicate
>> >> > +             with EEPROM sited by default address - 0x50)
>> >>
>> >> Don't we already have standard EEPROM properties that could be used
>> >> here?
>> >>
>> >
>> > If we do, just tell me which one. There are standard options:
>>
>> You can grep thru bindings as easily as I can. I can't do that for
>> everyone's binding.
>>
>
> It won't be necessary due to the next comment.
>
>> > "compatible, reg, pagesize, read-only". There isn't any connected with
>> > EEPROM actual size.
>> > Why so? Because standard EEPROM-drivers determine the device size from the
>> > compatible-string name. Such approach won't work in this case, because
>> > PCIe-switch and it EEPROM are actually two different devices. Look at the
>> > chain of the usual platform board design:
>> > Host <--- i2c ----> i2c-slave iface |PCIe-switch| i2c-master iface <--- i2c ---> EEPROM
>> >
>> > As you cas see the Host reaches EEPROM through the set of PCIe-switch
>> > i2c-interfaces. In order to properly get data from it my driver needs actual
>> > EEPROM size and it address in the i2c-master bus of the PCIe-switch, in
>> > addition to the standard reg-field, which is address of PCIe-switch i2c-slave
>> > interface and read-only parameter if EEPROM-device has got WP pin asserted.
>>
>> Ah, this needs to be much different than I thought. You need to model
>> (i.e. use the same binding) the EEPROM node just like it was directly
>> attached to the host. So this means you need the 2nd i2c bus modeled
>> which means you need the PCIe switch modeled. A rough outline of the
>> nodes would look like this:
>>
>> host-i2c: i2c {
>>   compatible ="host-i2c"
>> };
>>
>> pcie {
>>     pcie-switch {
>>         i2c-bus = <&host-i2c>;
>>         i2c-bus {
>>             eeprom@50 {
>>             };
>>         };
>>     };
>> };
>>
>> So this models the PCIe switch as a PCIe device, it has a phandle back
>> to it's controller since it's not a child of the i2c controller. Then
>> the devices on switches i2c bus are modeled as children of the switch.
>>
>> Alternatively, it could be described all as children of host-i2c node.
>> It's common for i2c devices to have downstream i2c buses. I2C muxes
>> are one example and there are bindings defined for all this. There's
>> also chips like mpu-6050 that have slave buses.
>>
>> Rob
>
> I think I understand what you says. However let me just bring some details
> to make things clear.
>
> First of all the driver doesn't do any PCI-Express-related work. The device
> !IDT PCI Express switch! just has two additional i2c interfaces: i2c-slave
> and i2c-master. As it is obvious from the bus-names i2c-slave is the interface,
> where IDT PCIe-switch device is actually slave. This interface can be reached
> from the host by ordinary i2c buses. i2c-master interface is connected to an
> i2c-bus, where IDT PCIe-switch is single master. This bus can have just one
> EEPROM device to store some initialization data. Host can send some specific
> smbus-packets to i2c-slave interface of IDT PCIe-switch in order to
> preinitialize EEPROM data, connected to i2c-master interface of the device.
>
> Additionally IDT PCIe-switch handles some special smbus packets coming to it
> i2c-slave interface to read/write its internal CSR. This interface can be
> used to debug the device, when there are problems with it usual PCI Express
> related functioning.
>
> So to speak, it wouldn't be good to have PCIe-switch declared in dts as a
> PCI-device, since PCI-bus is actually dynamically populated by PCI-core
> subsystem.

Why not? The DT is just extra data for what is not discoverable. Is
the device actually hotplugable and in a dynamic location/slot? If
not, then describing the device in DT is not uncommon. If it is
hotplugable, you still have same problem of knowing which I2C bus it
is on. Even if you know for your design, generally speaking you may
not know.

> According to what you said and the device/driver design I described, the
> following bindings can be suggested:
>
> i2c0: i2c@FFFF0000 {
>         compatible = "vendor,i2c-adapter";
>         #address-cells = <1>;
>         #size-cells = <0>;
>
>         idt_i2c_iface: idt@60 {
>                 compatible = "idt,89hpes32nt8ag2";
>                 reg = <0x60>;
>                 #address-cells = <1>;
>                 #size-cells = <0>;
>
>                 eeprom@51 {
>                         compatible = "at,24c64";
>                         reg = <0x51>;
>                         read-only;
>                 };
>         };
> };
>
> Suppose there is some host-i2c adapter like "vendor,i2c-adapter" and
> i2c-slave interface of IDT PCIe-switch is connected to it. In this way
> i2c-slave interface will be visible like ordinary i2c-device with just
> one subnode. This subnode explains the actual EEPROM connected to
> IDT PCIe-switch i2c-master interface.
>
> Does it look acceptable? It seems like your last suggestion. Is it?

That is the 2nd option. My concern is this may work for your immediate
case, but if you started to need to describe the PCIe interface it
would not work. Similarly, we started out describing USB hubs with I2C
interfaces this way and it has proven to be in adequate for some
cases. So we're moving to describing the USB hierarchy in DT. I'm
concerned that while it may work for you, if the PCIe interface has
any dependencies like regulators or something, then you would need to
have a PCIe node.

Rob

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-12-12 23:04             ` Rob Herring
@ 2016-12-13 14:08               ` Serge Semin
  0 siblings, 0 replies; 34+ messages in thread
From: Serge Semin @ 2016-12-13 14:08 UTC (permalink / raw)
  To: Rob Herring
  Cc: Greg Kroah-Hartman, Srinivas Kandagatla, Andrew Lunn,
	Mark Rutland, Sergey.Semin, linux-kernel, devicetree

On Mon, Dec 12, 2016 at 05:04:31PM -0600, Rob Herring <robh@kernel.org> wrote:
> On Mon, Dec 5, 2016 at 1:04 PM, Serge Semin <fancer.lancer@gmail.com> wrote:
> > On Mon, Dec 05, 2016 at 11:27:07AM -0600, Rob Herring <robh@kernel.org> wrote:
> >> On Mon, Dec 5, 2016 at 9:25 AM, Serge Semin <fancer.lancer@gmail.com> wrote:
> >> > On Mon, Dec 05, 2016 at 08:46:21AM -0600, Rob Herring <robh@kernel.org> wrote:
> >> >> On Tue, Nov 29, 2016 at 01:38:21AM +0300, Serge Semin wrote:
> >> >> > See cover-letter for changelog
> >> >> >
> >> >> > Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
> >> >> >
> >> >> > ---
> >> >> >  .../devicetree/bindings/misc/idt_89hpesx.txt       | 41 ++++++++++++++++++++++
> >> >>
> >> >> There's not a better location for this? I can't tell because you don't
> >> >> describe what the device is.
> >> >>
> >> >
> >> > The device is PCIe-switch EEPROM driver with additional debug-interface to
> >> > access the switch CSRs. EEPROM is accesses via a separate i2c-slave
> >> > interface of the switch.
> >> >
> >> > There might be another place to put the binding file in. There is a special
> >> > location for EEPROM drivers bindings - Documentation/devicetree/bindings/eeprom/ .
> >> > But as far as I understood from the files put in there, it's intended for
> >> > pure EEPROM drivers only. On the other hand the directory I've chosen:
> >> > Documentation/devicetree/bindings/misc/
> >> > mostly intended for some unusual devices. My device isn't usual, since it
> >> > has CSRs debug-interface as well. Additionally I've found
> >> > eeprom-93xx46.txt binding file there, which describes EEPROM bindings.
> >> >
> >> > Anyway if you find the file should be placed in
> >> > Documentation/devicetree/bindings/eeprom/ instead, I'll move it, it's not
> >> > that a big problem.
> >> >
> >
> > What about this comment? Shall the file be left at the path I placed it?
> >
> >> >> >  1 file changed, 41 insertions(+)
> >> >> >  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> >> >> >
> >> >> > diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> >> >> > index 0000000..469cc93
> >> >> > --- /dev/null
> >> >> > +++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
> >> >> > @@ -0,0 +1,41 @@
> >> >> > +EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
> >> >> > +
> >> >> > +Required properties:
> >> >> > +  - compatible : should be "<manufacturer>,<type>"
> >> >> > +            Basically there is only one manufacturer: idt, but some
> >> >> > +            compatible devices may be produced in future. Following devices
> >> >> > +            are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
> >> >> > +            89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
> >> >> > +            89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
> >> >> > +            89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
> >> >> > +            89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
> >> >> > +            89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
> >> >> > +            89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
> >> >> > +            89hpes64h16ag2;
> >> >> > +            89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
> >> >> > +            89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
> >> >> > +            89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
> >> >> > +            89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
> >> >> > +            89hpes48t12, 89hpes48t12g2.
> >> >> > +            Current implementation of the driver doesn't have any device-
> >> >>
> >> >> Driver capabilties are irrelevant to bindings.
> >> >>
> >> >
> >> > Why? I've told in the comment, that the devices actually differ by the CSRs
> >> > map. Even though it's not reflected in the code at the moment, the CSRs
> >> > read/write restrictions can be added by some concerned programmer in
> >> > future. But If I left something like "compatible : idt,89hpesx" device
> >> > only, it will be problematic to add that functionality.
> >>
> >> Bindings describe the h/w, not what the Linux, FreeBSD, etc. driver
> >> does. You don't want to be changing the binding doc when the driver
> >> changes.
> >>
> >> > Howbeit If you think it's not necessary and "compatible = idt,89hpesx" is
> >> > ok, it's perfectly fine for me to make it this way. The property will be
> >> > even simpler, than current approach.
> >>
> >> NO! That's not at all what I'm suggesting. Specific compatible strings
> >> are the right way to go for the reasons you give. You just don't need
> >> to state why here (because it is true for all bindings).
> >>
> >
> > Oh, I just misunderstood what you said. I'll discard the comment.
> >
> >> >> > +            specific functionalities. But since each of them differs
> >> >> > +            by registers mapping, CSRs read/write restrictions can be
> >> >> > +            added in future.
> >> >> > +  - reg :   I2C address of the IDT 89HPES device.
> >> >> > +
> >> >> > +Optional properties:
> >> >> > +  - read-only :     Parameterless property disables writes to the EEPROM
> >> >> > +  - idt,eesize : Size of EEPROM device connected to IDT 89HPES i2c-master bus
> >> >> > +            (default value is 4096 bytes if option isn't specified)
> >> >> > +  - idt,eeaddr : Custom address of EEPROM device
> >> >> > +            (If not specified IDT 89HPESx device will try to communicate
> >> >> > +             with EEPROM sited by default address - 0x50)
> >> >>
> >> >> Don't we already have standard EEPROM properties that could be used
> >> >> here?
> >> >>
> >> >
> >> > If we do, just tell me which one. There are standard options:
> >>
> >> You can grep thru bindings as easily as I can. I can't do that for
> >> everyone's binding.
> >>
> >
> > It won't be necessary due to the next comment.
> >
> >> > "compatible, reg, pagesize, read-only". There isn't any connected with
> >> > EEPROM actual size.
> >> > Why so? Because standard EEPROM-drivers determine the device size from the
> >> > compatible-string name. Such approach won't work in this case, because
> >> > PCIe-switch and it EEPROM are actually two different devices. Look at the
> >> > chain of the usual platform board design:
> >> > Host <--- i2c ----> i2c-slave iface |PCIe-switch| i2c-master iface <--- i2c ---> EEPROM
> >> >
> >> > As you cas see the Host reaches EEPROM through the set of PCIe-switch
> >> > i2c-interfaces. In order to properly get data from it my driver needs actual
> >> > EEPROM size and it address in the i2c-master bus of the PCIe-switch, in
> >> > addition to the standard reg-field, which is address of PCIe-switch i2c-slave
> >> > interface and read-only parameter if EEPROM-device has got WP pin asserted.
> >>
> >> Ah, this needs to be much different than I thought. You need to model
> >> (i.e. use the same binding) the EEPROM node just like it was directly
> >> attached to the host. So this means you need the 2nd i2c bus modeled
> >> which means you need the PCIe switch modeled. A rough outline of the
> >> nodes would look like this:
> >>
> >> host-i2c: i2c {
> >>   compatible ="host-i2c"
> >> };
> >>
> >> pcie {
> >>     pcie-switch {
> >>         i2c-bus = <&host-i2c>;
> >>         i2c-bus {
> >>             eeprom@50 {
> >>             };
> >>         };
> >>     };
> >> };
> >>
> >> So this models the PCIe switch as a PCIe device, it has a phandle back
> >> to it's controller since it's not a child of the i2c controller. Then
> >> the devices on switches i2c bus are modeled as children of the switch.
> >>
> >> Alternatively, it could be described all as children of host-i2c node.
> >> It's common for i2c devices to have downstream i2c buses. I2C muxes
> >> are one example and there are bindings defined for all this. There's
> >> also chips like mpu-6050 that have slave buses.
> >>
> >> Rob
> >
> > I think I understand what you says. However let me just bring some details
> > to make things clear.
> >
> > First of all the driver doesn't do any PCI-Express-related work. The device
> > !IDT PCI Express switch! just has two additional i2c interfaces: i2c-slave
> > and i2c-master. As it is obvious from the bus-names i2c-slave is the interface,
> > where IDT PCIe-switch device is actually slave. This interface can be reached
> > from the host by ordinary i2c buses. i2c-master interface is connected to an
> > i2c-bus, where IDT PCIe-switch is single master. This bus can have just one
> > EEPROM device to store some initialization data. Host can send some specific
> > smbus-packets to i2c-slave interface of IDT PCIe-switch in order to
> > preinitialize EEPROM data, connected to i2c-master interface of the device.
> >
> > Additionally IDT PCIe-switch handles some special smbus packets coming to it
> > i2c-slave interface to read/write its internal CSR. This interface can be
> > used to debug the device, when there are problems with it usual PCI Express
> > related functioning.
> >
> > So to speak, it wouldn't be good to have PCIe-switch declared in dts as a
> > PCI-device, since PCI-bus is actually dynamically populated by PCI-core
> > subsystem.
> 
> Why not? The DT is just extra data for what is not discoverable. Is
> the device actually hotplugable and in a dynamic location/slot? If
> not, then describing the device in DT is not uncommon. If it is
> hotplugable, you still have same problem of knowing which I2C bus it
> is on. Even if you know for your design, generally speaking you may
> not know.
> 

Device isn't hotplugable, it's always placed on the circuit. So to speak it is
always known which i2c bus it's placed on. That's why I placed the device
description in the dts.

> > According to what you said and the device/driver design I described, the
> > following bindings can be suggested:
> >
> > i2c0: i2c@FFFF0000 {
> >         compatible = "vendor,i2c-adapter";
> >         #address-cells = <1>;
> >         #size-cells = <0>;
> >
> >         idt_i2c_iface: idt@60 {
> >                 compatible = "idt,89hpes32nt8ag2";
> >                 reg = <0x60>;
> >                 #address-cells = <1>;
> >                 #size-cells = <0>;
> >
> >                 eeprom@51 {
> >                         compatible = "at,24c64";
> >                         reg = <0x51>;
> >                         read-only;
> >                 };
> >         };
> > };
> >
> > Suppose there is some host-i2c adapter like "vendor,i2c-adapter" and
> > i2c-slave interface of IDT PCIe-switch is connected to it. In this way
> > i2c-slave interface will be visible like ordinary i2c-device with just
> > one subnode. This subnode explains the actual EEPROM connected to
> > IDT PCIe-switch i2c-master interface.
> >
> > Does it look acceptable? It seems like your last suggestion. Is it?
> 
> That is the 2nd option. My concern is this may work for your immediate
> case, but if you started to need to describe the PCIe interface it
> would not work. Similarly, we started out describing USB hubs with I2C
> interfaces this way and it has proven to be in adequate for some
> cases. So we're moving to describing the USB hierarchy in DT. I'm
> concerned that while it may work for you, if the PCIe interface has
> any dependencies like regulators or something, then you would need to
> have a PCIe node.
> 
> Rob

Alright then. I'll develop the second option and resend the patchset.

-Sergey

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-11-29 22:27   ` [PATCH v3 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2016-11-29 22:27     ` [PATCH v3 1/2] " Serge Semin
  2016-11-29 22:27     ` [PATCH v3 2/2] eeprom: Add IDT 89HPESx driver dts-binding file Serge Semin
@ 2016-12-13 14:22     ` Serge Semin
  2016-12-13 14:22       ` [PATCH v4 1/2] " Serge Semin
                         ` (2 more replies)
  2 siblings, 3 replies; 34+ messages in thread
From: Serge Semin @ 2016-12-13 14:22 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

Changelog v3:
- Get rid of dev_*_idt() macros
- Replace to_pdev_kobj() macro with naked dev_get_drvdata() call
- Return naked 0 instead of SUCCESS macro
- IDT CSR debug file is moved to debugfs
- BIN_ATTR_RW is used to declare sysfs binary attribute
- Moved bindings file to a separate patch
- Need to create a specific bin_attribute structure for each device
- Perform a few read retries with delays if EEPROM is busy

Changelog v4:
- Make 89HPESx device bindings to have one EEPROM subnode
- Alter 89HPESx device bindings text

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>

Serge Semin (2):
  eeprom: Add IDT 89HPESx EEPROM/CSR driver
  eeprom: Add IDT 89HPESx driver bindings file

 .../devicetree/bindings/misc/idt_89hpesx.txt       |   44 +
 drivers/misc/eeprom/Kconfig                        |   10 +
 drivers/misc/eeprom/Makefile                       |    1 +
 drivers/misc/eeprom/idt_89hpesx.c                  | 1634 ++++++++++++++++++++
 4 files changed, 1689 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

-- 
2.6.6

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-12-13 14:22     ` [PATCH v4 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
@ 2016-12-13 14:22       ` Serge Semin
  2017-01-11  8:21         ` Greg KH
  2016-12-13 14:22       ` [PATCH v4 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
  2017-01-13 12:16       ` [PATCH v5 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2 siblings, 1 reply; 34+ messages in thread
From: Serge Semin @ 2016-12-13 14:22 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

  This driver provides an access to EEPROM of IDT PCIe-switches. IDT PCIe-
switches expose a simple SMBus interface to perform IO-operations from/to
EEPROM, which is located at private (so called Master) SMBus. The driver
creates a simple binary sysfs-file to have an access to the EEPROM using
the SMBus-slave interface in the i2c-device susfs-directory:
     /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
In case if read-only flag is specified at dts-node of the device, User-space
applications won't be able to write to the EEPROM sysfs-node.

  Additionally IDT 89HPESx SMBus interface has an ability to read/write
values of device CSRs. This driver exposes debugfs-file to perform simple
IO-operations using that ability for just basic debug purpose. Particularly
the next file is created in the specific debugfs-directory:
     /sys/kernel/debug/idt_csr/
Format of the debugfs-file value is:
     $ cat /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
     <CSR address>:<CSR value>
So reading the content of the file gives current CSR address and it value.
If User-space application wishes to change current CSR address, it can just
write a proper value to the sysfs-file:
     $ echo "<CSR address>" >
         /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>
If it wants to change the CSR value as well, the format of the write
operation is:
     $ echo "<CSR address>:<CSR value>" > \
         /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
CSR address and value can be any of hexadecimal, decimal or octal format.

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
 drivers/misc/eeprom/Kconfig       |   10 +
 drivers/misc/eeprom/Makefile      |    1 +
 drivers/misc/eeprom/idt_89hpesx.c | 1634 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1645 insertions(+)
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index c4e41c2..de58762 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -100,4 +100,14 @@ config EEPROM_DIGSY_MTC_CFG
 
 	  If unsure, say N.
 
+config EEPROM_IDT_89HPESX
+	tristate "IDT 89HPESx PCIe-swtiches EEPROM / CSR support"
+	depends on I2C && SYSFS
+	help
+	  Enable this driver to get read/write access to EEPROM / CSRs
+	  over IDT PCIe-swtich i2c-slave interface.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called idt_89hpesx.
+
 endmenu
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index fc1e81d..90a5262 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o
 obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
 obj-$(CONFIG_EEPROM_93XX46)	+= eeprom_93xx46.o
 obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
+obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c
new file mode 100644
index 0000000..664e315
--- /dev/null
+++ b/drivers/misc/eeprom/idt_89hpesx.c
@@ -0,0 +1,1634 @@
+/*
+ *   This file is provided under a GPLv2 license.  When using or
+ *   redistributing this file, you may do so under that license.
+ *
+ *   GPL LICENSE SUMMARY
+ *
+ *   Copyright (C) 2016 T-Platforms. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or modify it
+ *   under the terms and conditions of the GNU General Public License,
+ *   version 2, as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful, but WITHOUT
+ *   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ *   more details.
+ *
+ *   You should have received a copy of the GNU General Public License along
+ *   with this program; if not, it can be found <http://www.gnu.org/licenses/>.
+ *
+ *   The full GNU General Public License is included in this distribution in
+ *   the file called "COPYING".
+ *
+ *   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.
+ *
+ * IDT PCIe-switch NTB Linux driver
+ *
+ * Contact Information:
+ * Serge Semin <fancer.lancer@gmail.com>, <Sergey.Semin@t-platforms.ru>
+ */
+/*
+ *           NOTE of the IDT 89HPESx SMBus-slave interface driver
+ *    This driver primarily is developed to have an access to EEPROM device of
+ * IDT PCIe-switches. IDT provides a simple SMBus interface to perform IO-
+ * operations from/to EEPROM, which is located at private (so called Master)
+ * SMBus of switches. Using that interface this the driver creates a simple
+ * binary sysfs-file in the device directory:
+ * /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
+ * In case if read-only flag is specified in the dts-node of device desription,
+ * User-space applications won't be able to write to the EEPROM sysfs-node.
+ *    Additionally IDT 89HPESx SMBus interface has an ability to write/read
+ * data of device CSRs. This driver exposes debugf-file to perform simple IO
+ * operations using that ability for just basic debug purpose. Particularly
+ * next file is created in the specific debugfs-directory:
+ * /sys/kernel/debug/idt_csr/
+ * Format of the debugfs-node is:
+ * $ cat /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * <CSR address>:<CSR value>
+ * So reading the content of the file gives current CSR address and it value.
+ * If User-space application wishes to change current CSR address,
+ * it can just write a proper value to the sysfs-file:
+ * $ echo "<CSR address>" > /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>
+ * If it wants to change the CSR value as well, the format of the write
+ * operation is:
+ * $ echo "<CSR address>:<CSR value>" > \
+ *        /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * CSR address and value can be any of hexadecimal, decimal or octal format.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/debugfs.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/pci_ids.h>
+#include <linux/delay.h>
+
+#define IDT_NAME		"89hpesx"
+#define IDT_89HPESX_DESC	"IDT 89HPESx SMBus-slave interface driver"
+#define IDT_89HPESX_VER		"1.0"
+
+MODULE_DESCRIPTION(IDT_89HPESX_DESC);
+MODULE_VERSION(IDT_89HPESX_VER);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("T-platforms");
+
+/*
+ * csr_dbgdir - CSR read/write operations Debugfs directory
+ */
+static struct dentry *csr_dbgdir;
+
+/*
+ * struct idt_89hpesx_dev - IDT 89HPESx device data structure
+ * @eesize:	Size of EEPROM in bytes (calculated from "idt,eecompatible")
+ * @eero:	EEPROM Read-only flag
+ * @eeaddr:	EEPROM custom address
+ *
+ * @inieecmd:	Initial cmd value for EEPROM read/write operations
+ * @inicsrcmd:	Initial cmd value for CSR read/write operations
+ * @iniccode:	Initialial command code value for IO-operations
+ *
+ * @csr:	CSR address to perform read operation
+ *
+ * @smb_write:	SMBus write method
+ * @smb_read:	SMBus read method
+ * @smb_mtx:	SMBus mutex
+ *
+ * @client:	i2c client used to perform IO operations
+ *
+ * @ee_file:	EEPROM read/write sysfs-file
+ * @csr_file:	CSR read/write debugfs-node
+ */
+struct idt_smb_seq;
+struct idt_89hpesx_dev {
+	u32 eesize;
+	bool eero;
+	u8 eeaddr;
+
+	u8 inieecmd;
+	u8 inicsrcmd;
+	u8 iniccode;
+
+	atomic_t csr;
+
+	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
+	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
+	struct mutex smb_mtx;
+
+	struct i2c_client *client;
+
+	struct bin_attribute *ee_file;
+	struct dentry *csr_dir;
+	struct dentry *csr_file;
+};
+
+/*
+ * struct idt_smb_seq - sequence of data to be read/written from/to IDT 89HPESx
+ * @ccode:	SMBus command code
+ * @bytecnt:	Byte count of operation
+ * @data:	Data to by written
+ */
+struct idt_smb_seq {
+	u8 ccode;
+	u8 bytecnt;
+	u8 *data;
+};
+
+/*
+ * struct idt_eeprom_seq - sequence of data to be read/written from/to EEPROM
+ * @cmd:	Transaction CMD
+ * @eeaddr:	EEPROM custom address
+ * @memaddr:	Internal memory address of EEPROM
+ * @data:	Data to be written at the memory address
+ */
+struct idt_eeprom_seq {
+	u8 cmd;
+	u8 eeaddr;
+	u16 memaddr;
+	u8 data;
+} __packed;
+
+/*
+ * struct idt_csr_seq - sequence of data to be read/written from/to CSR
+ * @cmd:	Transaction CMD
+ * @csraddr:	Internal IDT device CSR address
+ * @data:	Data to be read/written from/to the CSR address
+ */
+struct idt_csr_seq {
+	u8 cmd;
+	u16 csraddr;
+	u32 data;
+} __packed;
+
+/*
+ * SMBus command code macros
+ * @CCODE_END:		Indicates the end of transaction
+ * @CCODE_START:	Indicates the start of transaction
+ * @CCODE_CSR:		CSR read/write transaction
+ * @CCODE_EEPROM:	EEPROM read/write transaction
+ * @CCODE_BYTE:		Supplied data has BYTE length
+ * @CCODE_WORD:		Supplied data has WORD length
+ * @CCODE_BLOCK:	Supplied data has variable length passed in bytecnt
+ *			byte right following CCODE byte
+ */
+#define CCODE_END	((u8)0x01)
+#define CCODE_START	((u8)0x02)
+#define CCODE_CSR	((u8)0x00)
+#define CCODE_EEPROM	((u8)0x04)
+#define CCODE_BYTE	((u8)0x00)
+#define CCODE_WORD	((u8)0x20)
+#define CCODE_BLOCK	((u8)0x40)
+#define CCODE_PEC	((u8)0x80)
+
+/*
+ * EEPROM command macros
+ * @EEPROM_OP_WRITE:	EEPROM write operation
+ * @EEPROM_OP_READ:	EEPROM read operation
+ * @EEPROM_USA:		Use specified address of EEPROM
+ * @EEPROM_NAERR:	EEPROM device is not ready to respond
+ * @EEPROM_LAERR:	EEPROM arbitration loss error
+ * @EEPROM_MSS:		EEPROM misplace start & stop bits error
+ * @EEPROM_WR_CNT:	Bytes count to perform write operation
+ * @EEPROM_WRRD_CNT:	Bytes count to write before reading
+ * @EEPROM_RD_CNT:	Bytes count to perform read operation
+ * @EEPROM_DEF_SIZE:	Fall back size of EEPROM
+ * @EEPROM_DEF_ADDR:	Defatul EEPROM address
+ * @EEPROM_TOUT:	Timeout before retry read operation if eeprom is busy
+ */
+#define EEPROM_OP_WRITE	((u8)0x00)
+#define EEPROM_OP_READ	((u8)0x01)
+#define EEPROM_USA	((u8)0x02)
+#define EEPROM_NAERR	((u8)0x08)
+#define EEPROM_LAERR    ((u8)0x10)
+#define EEPROM_MSS	((u8)0x20)
+#define EEPROM_WR_CNT	((u8)5)
+#define EEPROM_WRRD_CNT	((u8)4)
+#define EEPROM_RD_CNT	((u8)5)
+#define EEPROM_DEF_SIZE	((u16)4096)
+#define EEPROM_DEF_ADDR	((u8)0x50)
+#define EEPROM_TOUT	(100)
+
+/*
+ * CSR command macros
+ * @CSR_DWE:		Enable all four bytes of the operation
+ * @CSR_OP_WRITE:	CSR write operation
+ * @CSR_OP_READ:	CSR read operation
+ * @CSR_RERR:		Read operation error
+ * @CSR_WERR:		Write operation error
+ * @CSR_WR_CNT:		Bytes count to perform write operation
+ * @CSR_WRRD_CNT:	Bytes count to write before reading
+ * @CSR_RD_CNT:		Bytes count to perform read operation
+ * @CSR_MAX:		Maximum CSR address
+ * @CSR_DEF:		Default CSR address
+ * @CSR_REAL_ADDR:	CSR real unshifted address
+ */
+#define CSR_DWE			((u8)0x0F)
+#define CSR_OP_WRITE		((u8)0x00)
+#define CSR_OP_READ		((u8)0x10)
+#define CSR_RERR		((u8)0x40)
+#define CSR_WERR		((u8)0x80)
+#define CSR_WR_CNT		((u8)7)
+#define CSR_WRRD_CNT		((u8)3)
+#define CSR_RD_CNT		((u8)7)
+#define CSR_MAX			((u32)0x3FFFF)
+#define CSR_DEF			((u16)0x0000)
+#define CSR_REAL_ADDR(val)	((unsigned int)val << 2)
+
+/*
+ * IDT 89HPESx basic register
+ * @IDT_VIDDID_CSR:	PCIe VID and DID of IDT 89HPESx
+ * @IDT_VID_MASK:	Mask of VID
+ */
+#define IDT_VIDDID_CSR	((u32)0x0000)
+#define IDT_VID_MASK	((u32)0xFFFF)
+
+/*
+ * IDT 89HPESx can send NACK when new command is sent before previous one
+ * fininshed execution. In this case driver retries operation
+ * certain times.
+ * @RETRY_CNT:		Number of retries before giving up and fail
+ * @idt_smb_safe:	Generate a retry loop on corresponding SMBus method
+ */
+#define RETRY_CNT (128)
+#define idt_smb_safe(ops, args...) ({ \
+	int __retry = RETRY_CNT; \
+	s32 __sts; \
+	do { \
+		__sts = i2c_smbus_ ## ops ## _data(args); \
+	} while (__retry-- && __sts < 0); \
+	__sts; \
+})
+
+/*===========================================================================
+ *                         i2c bus level IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_smb_write_byte() - SMBus write method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_byte(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied data sending byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Send data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_byte() - SMBus read method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_byte(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied buffer receiving byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Read data from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_word() - SMBus write method when I2C_SMBUS_BYTE_DATA and
+ *                        I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_word(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data sending two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Send word data to the device */
+		sts = idt_smb_safe(write_word, pdev->client, ccode,
+			*(u16 *)&seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	/* If there is odd number of bytes then send just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Send byte data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_word() - SMBus read method when I2C_SMBUS_BYTE_DATA and
+ *                       I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_word(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data reading two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Read word data from the device */
+		sts = idt_smb_safe(read_word, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		*(u16 *)&seq->data[idx] = (u16)sts;
+	}
+
+	/* If there is odd number of bytes then receive just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Read last data byte from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_block() - SMBus write method when I2C_SMBUS_BLOCK_DATA
+ *                         operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_block(struct idt_89hpesx_dev *pdev,
+			       const struct idt_smb_seq *seq)
+{
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send block of data to the device */
+	return idt_smb_safe(write_block, pdev->client, ccode, seq->bytecnt,
+		seq->data);
+}
+
+/*
+ * idt_smb_read_block() - SMBus read method when I2C_SMBUS_BLOCK_DATA
+ *                        operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_block(struct idt_89hpesx_dev *pdev,
+			      struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read block of data from the device */
+	sts = idt_smb_safe(read_block, pdev->client, ccode, seq->data);
+	if (sts != seq->bytecnt)
+		return (sts < 0 ? sts : -ENODATA);
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_i2c_block() - SMBus write method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                             operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ *
+ * NOTE It's usual SMBus write block operation, except the actual data length is
+ * sent as first byte of data
+ */
+static int idt_smb_write_i2c_block(struct idt_89hpesx_dev *pdev,
+				   const struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the data to send. Length byte must be added prior the data */
+	buf[0] = seq->bytecnt;
+	memcpy(&buf[1], seq->data, seq->bytecnt);
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send length and block of data to the device */
+	return idt_smb_safe(write_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+}
+
+/*
+ * idt_smb_read_i2c_block() - SMBus read method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                            operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ *
+ * NOTE It's usual SMBus read block operation, except the actual data length is
+ * retrieved as first byte of data
+ */
+static int idt_smb_read_i2c_block(struct idt_89hpesx_dev *pdev,
+				  struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+	s32 sts;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read length and block of data from the device */
+	sts = idt_smb_safe(read_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+	if (sts != seq->bytecnt + 1)
+		return (sts < 0 ? sts : -ENODATA);
+	if (buf[0] != seq->bytecnt)
+		return -ENODATA;
+
+	/* Copy retrieved data to the output data buffer */
+	memcpy(seq->data, &buf[1], seq->bytecnt);
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          EEPROM IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_eeprom_read_byte() - read just one byte from EEPROM
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_read_byte(struct idt_89hpesx_dev *pdev, u16 memaddr,
+				u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret, retry;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/*
+	 * Sometimes EEPROM may respond with NACK if it's busy with previous
+	 * operation, so we need to perform a few attempts of read cycle
+	 */
+	retry = RETRY_CNT;
+	do {
+		/* Send EEPROM memory address to read data from */
+		smbseq.bytecnt = EEPROM_WRRD_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to init eeprom addr 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Perform read operation */
+		smbseq.bytecnt = EEPROM_RD_CNT;
+		ret = pdev->smb_read(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to read eeprom data 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Restart read operation if the device is busy */
+		if (retry && (eeseq.cmd & EEPROM_NAERR)) {
+			dev_dbg(dev, "EEPROM busy, retry reading after %d ms",
+				EEPROM_TOUT);
+			msleep(EEPROM_TOUT);
+			continue;
+		}
+
+		/* Check whether IDT successfully read data from EEPROM */
+		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
+			dev_err(dev,
+				"Communication with eeprom failed, cmd 0x%hhx",
+				eeseq.cmd);
+			ret = -EREMOTEIO;
+			break;
+		}
+
+		/* Save retrieved data and exit the loop */
+		*data = eeseq.data;
+		break;
+	} while (retry--);
+
+	/* Return the status of operation */
+	return ret;
+}
+
+/*
+ * idt_eeprom_write() - EEPROM write operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to be written
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_write(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			    const u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+	u16 idx;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/* Send data byte-by-byte, checking if it is successfully written */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Perform write operation */
+		smbseq.bytecnt = EEPROM_WR_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_WRITE;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		eeseq.data = data[idx];
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev,
+				"Failed to write 0x%04hx:0x%02hhx to eeprom",
+				memaddr, data[idx]);
+			goto err_mutex_unlock;
+		}
+
+		/*
+		 * Check whether the data is successfully written by reading
+		 * from the same EEPROM memory address.
+		 */
+		eeseq.data = ~data[idx];
+		ret = idt_eeprom_read_byte(pdev, memaddr, &eeseq.data);
+		if (ret != 0)
+			goto err_mutex_unlock;
+
+		/* Check whether the read byte is the same as written one */
+		if (eeseq.data != data[idx]) {
+			dev_err(dev, "Values don't match 0x%02hhx != 0x%02hhx",
+				eeseq.data, data[idx]);
+			ret = -EREMOTEIO;
+			goto err_mutex_unlock;
+		}
+
+		/* Unlock IDT SMBus device */
+err_mutex_unlock:
+		mutex_unlock(&pdev->smb_mtx);
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_eeprom_read() - EEPROM read operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to read
+ * @buf:	Buffer to read data to
+ */
+static int idt_eeprom_read(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			   u8 *buf)
+{
+	int ret;
+	u16 idx;
+
+	/* Read data byte-by-byte, retrying if it wasn't successful */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Just read the byte to the buffer */
+		ret = idt_eeprom_read_byte(pdev, memaddr, &buf[idx]);
+
+		/* Unlock IDT SMBus device */
+		mutex_unlock(&pdev->smb_mtx);
+
+		/* Return error if read operation failed */
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          CSR IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_csr_write() - CSR write operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_write(struct idt_89hpesx_dev *pdev, u16 csraddr,
+			 const u32 data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Perform write operation */
+	smbseq.bytecnt = CSR_WR_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_WRITE;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	csrseq.data = cpu_to_le32(data);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write 0x%04x: 0x%04x to csr",
+			CSR_REAL_ADDR(csraddr), data);
+		goto err_mutex_unlock;
+	}
+
+	/* Send CSR address to read data from */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*
+ * idt_csr_read() - CSR read operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_read(struct idt_89hpesx_dev *pdev, u16 csraddr, u32 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Send CSR register address before reading it */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04hx",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Save data retrieved from IDT */
+	*data = le32_to_cpu(csrseq.data);
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*===========================================================================
+ *                          Sysfs/debugfs-nodes IO-operations
+ *===========================================================================
+ */
+
+/*
+ * eeprom_write() - EEPROM sysfs-node write callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t eeprom_write(struct file *filp, struct kobject *kobj,
+			    struct bin_attribute *attr,
+			    char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(kobj_to_dev(kobj));
+
+	/* Perform EEPROM write operation */
+	ret = idt_eeprom_write(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * eeprom_read() - EEPROM sysfs-node read callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
+			   struct bin_attribute *attr,
+			   char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(kobj_to_dev(kobj));
+
+	/* Perform EEPROM read operation */
+	ret = idt_eeprom_read(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_write() - CSR debugfs-node write callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to read data from
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It accepts either "0x<reg addr>:0x<value>" for saving register address
+ * and writing value to specified DWORD register or "0x<reg addr>" for
+ * just saving register address in order to perform next read operation.
+ *
+ * WARNING No spaces are allowed. Incoming string must be strictly formated as:
+ * "<reg addr>:<value>". Register address must be aligned within 4 bytes
+ * (one DWORD).
+ */
+static ssize_t idt_dbgfs_csr_write(struct file *filep, const char __user *ubuf,
+				   size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	char *colon_ch, *csraddr_str, *csrval_str;
+	int ret, csraddr_len, csrval_len;
+	u32 csraddr, csrval;
+	char *buf;
+
+	/* Copy data from User-space */
+	buf = kmalloc(count + 1, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = simple_write_to_buffer(buf, count, offp, ubuf, count);
+	if (ret < 0)
+		goto free_buf;
+	buf[count] = 0;
+
+	/* Find position of colon in the buffer */
+	colon_ch = strnchr(buf, count, ':');
+
+	/*
+	 * If there is colon passed then new CSR value should be parsed as
+	 * well, so allocate buffer for CSR address substring.
+	 * If no colon is found, then string must have just one number with
+	 * no new CSR value
+	 */
+	if (colon_ch != NULL) {
+		csraddr_len = colon_ch - buf;
+		csraddr_str =
+			kmalloc(sizeof(char)*(csraddr_len + 1), GFP_KERNEL);
+		if (csraddr_str == NULL)
+			return -ENOMEM;
+		/* Copy the register address to the substring buffer */
+		strncpy(csraddr_str, buf, csraddr_len);
+		csraddr_str[csraddr_len] = '\0';
+		/* Register value must follow the colon */
+		csrval_str = colon_ch + 1;
+		csrval_len = strnlen(csrval_str, count - csraddr_len);
+	} else /* if (str_colon == NULL) */ {
+		csraddr_str = (char *)buf; /* Just to shut warning up */
+		csraddr_len = strnlen(csraddr_str, count);
+		csrval_str = NULL;
+		csrval_len = 0;
+	}
+
+	/* Convert CSR address to u32 value */
+	ret = kstrtou32(csraddr_str, 0, &csraddr);
+	if (ret != 0)
+		goto free_csraddr_str;
+
+	/* Check whether passed register address is valid */
+	if (csraddr > CSR_MAX || !IS_ALIGNED(csraddr, SZ_4)) {
+		ret = -EINVAL;
+		goto free_csraddr_str;
+	}
+
+	/* Shift register address to the right so to have u16 address */
+	csraddr >>= 2;
+
+	/* Parse new CSR value and send it to IDT, if colon has been found */
+	if (colon_ch != NULL) {
+		ret = kstrtou32(csrval_str, 0, &csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+
+		ret = idt_csr_write(pdev, (u16)csraddr, csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+	}
+
+	/* Save CSR address in the data structure for future read operations */
+	atomic_set(&pdev->csr, (int)csraddr);
+
+	/* Free memory only if colon has been found */
+free_csraddr_str:
+	if (colon_ch != NULL)
+		kfree(csraddr_str);
+
+	/* Free buffer allocated for data retrieved from User-space */
+free_buf:
+	kfree(buf);
+
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_read() - CSR debugfs-node read callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to write data to
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It just prints the pair "0x<reg addr>:0x<value>" to passed buffer.
+ */
+#define CSRBUF_SIZE	((size_t)32)
+static ssize_t idt_dbgfs_csr_read(struct file *filep, char __user *ubuf,
+				  size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	u32 csraddr, csrval;
+	char buf[CSRBUF_SIZE];
+	int ret, size;
+
+	/* Read current CSR address */
+	csraddr = atomic_read(&pdev->csr);
+
+	/* Perform CSR read operation */
+	ret = idt_csr_read(pdev, (u16)csraddr, &csrval);
+	if (ret != 0)
+		return ret;
+
+	/* Shift register address to the left so to have real address */
+	csraddr <<= 2;
+
+	/* Print the "0x<reg addr>:0x<value>" to buffer */
+	size = snprintf(buf, CSRBUF_SIZE, "0x%05x:0x%08x\n",
+		(unsigned int)csraddr, (unsigned int)csrval);
+
+	/* Copy data to User-space */
+	return simple_read_from_buffer(ubuf, count, offp, buf, size);
+}
+
+/*
+ * eeprom_attribute - EEPROM sysfs-node attributes
+ *
+ * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
+ * be read-only as well if the corresponding flag is specified in OF node.
+ */
+static BIN_ATTR_RW(eeprom, EEPROM_DEF_SIZE);
+
+/*
+ * csr_dbgfs_ops - CSR debugfs-node read/write operations
+ */
+static const struct file_operations csr_dbgfs_ops = {
+	.owner = THIS_MODULE,
+	.open = simple_open,
+	.write = idt_dbgfs_csr_write,
+	.read = idt_dbgfs_csr_read
+};
+
+/*===========================================================================
+ *                       Driver init/deinit methods
+ *===========================================================================
+ */
+
+/*
+ * idt_set_defval() - disable EEPROM access by default
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_set_defval(struct idt_89hpesx_dev *pdev)
+{
+	/* If OF info is missing then use next values */
+	pdev->eesize = 0;
+	pdev->eero = true;
+	pdev->inieecmd = 0;
+	pdev->eeaddr = 0;
+}
+
+#ifdef CONFIG_OF
+static const struct i2c_device_id ee_ids[];
+/*
+ * idt_ee_match_id() - check whether the node belongs to compatible EEPROMs
+ */
+static const struct i2c_device_id *idt_ee_match_id(struct device_node *node)
+{
+	const struct i2c_device_id *id = ee_ids;
+	char devname[I2C_NAME_SIZE];
+
+	/* Retrieve the device name without manufacturer name */
+	if (of_modalias_node(node, devname, sizeof(devname)))
+		return NULL;
+
+	/* Search through the device name */
+        while (id->name[0]) {
+                if (strcmp(devname, id->name) == 0)
+                        return id;
+                id++;
+        }
+        return NULL;
+}
+
+/*
+ * idt_get_ofdata() - get IDT i2c-device parameters from device tree
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	const struct device_node *node = pdev->client->dev.of_node;
+	struct device *dev = &pdev->client->dev;
+
+	/* Read dts node parameters */
+	if (node) {
+		const struct i2c_device_id *ee_id = NULL;
+		struct device_node *child;
+		const __be32 *addr_be;
+		int len;
+
+		/* Walk through all child nodes looking for compatible one */
+		for_each_available_child_of_node(node, child) {
+			ee_id = idt_ee_match_id(child);
+			if (IS_ERR_OR_NULL(ee_id)) {
+				dev_warn(dev, "Skip unsupported child node %s",
+					child->full_name);
+				continue;
+			} else
+				break;
+		}
+
+		/* If there is no child EEPROM device, then set zero size */
+		if (!ee_id) {
+			idt_set_defval(pdev);
+			return;
+		}
+
+		/* Retrieve EEPROM size */
+		pdev->eesize = (u32)ee_id->driver_data;
+
+		/* Get custom EEPROM address from 'reg' attribute */
+		addr_be = of_get_property(child, "reg", &len);
+		if (!addr_be || (len < sizeof(*addr_be))) {
+			dev_warn(dev, "No reg on %s, use default address %d",
+				child->full_name, EEPROM_DEF_ADDR);
+			pdev->inieecmd = 0;
+			pdev->eeaddr = EEPROM_DEF_ADDR << 1;
+		} else {
+			pdev->inieecmd = EEPROM_USA;
+			pdev->eeaddr = be32_to_cpup(addr_be) << 1;
+		}
+
+		/* Check EEPROM 'read-only' flag */
+		if (of_get_property(child, "read-only", NULL))
+			pdev->eero = true;
+		else /* if (!of_get_property(node, "read-only", NULL)) */
+			pdev->eero = false;
+
+		dev_dbg(dev, "EEPROM of %u bytes found by %hhu",
+			pdev->eesize, pdev->eeaddr);
+	} else {
+		dev_warn(dev, "No dts node, EEPROM access disabled");
+		idt_set_defval(pdev);
+	}
+}
+#else
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	dev_warn(dev, "OF table is unsupported, EEPROM access disabled");
+
+	/* Nothing we can do, just set the default values */
+	idt_set_defval(pdev);
+}
+#endif /* CONFIG_OF */
+
+/*
+ * idt_create_pdev() - create and init data structure of the driver
+ * @client:	i2c client of IDT PCIe-switch device
+ */
+static struct idt_89hpesx_dev *idt_create_pdev(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev;
+
+	/* Allocate memory for driver data */
+	pdev = devm_kmalloc(&client->dev, sizeof(struct idt_89hpesx_dev),
+		GFP_KERNEL);
+	if (pdev == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	/* Initialize basic fields of the data */
+	pdev->client = client;
+	i2c_set_clientdata(client, pdev);
+
+	/* Read OF nodes information */
+	idt_get_ofdata(pdev);
+
+	/* Initialize basic CSR CMD field - use full DWORD-sized r/w ops */
+	pdev->inicsrcmd = CSR_DWE;
+	atomic_set(&pdev->csr, CSR_DEF);
+
+	/* Enable Packet Error Checking if it's supported by adapter */
+	if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) {
+		pdev->iniccode = CCODE_PEC;
+		client->flags |= I2C_CLIENT_PEC;
+	} else /* PEC is unsupported */ {
+		pdev->iniccode = 0;
+	}
+
+	dev_dbg(&client->dev, "IDT 89HPESx data created");
+
+	return pdev;
+}
+
+/*
+ * idt_free_pdev() - free data structure of the driver
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_free_pdev(struct idt_89hpesx_dev *pdev)
+{
+	/* Clear driver data from device private field */
+	i2c_set_clientdata(pdev->client, NULL);
+
+	/* Just free memory allocated for data */
+	devm_kfree(&pdev->client->dev, pdev);
+
+	dev_dbg(&pdev->client->dev, "IDT 89HPESx data discarded");
+}
+
+/*
+ * idt_set_smbus_ops() - set supported SMBus operations
+ * @pdev:	Pointer to the driver data
+ * Return status of smbus check operations
+ */
+static int idt_set_smbus_ops(struct idt_89hpesx_dev *pdev)
+{
+	struct i2c_adapter *adapter = pdev->client->adapter;
+	struct device *dev = &pdev->client->dev;
+
+	/* Check i2c adapter read functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_READ_BLOCK_DATA)) {
+		pdev->smb_read = idt_smb_read_block;
+		dev_dbg(dev, "SMBus block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+		pdev->smb_read = idt_smb_read_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_word;
+		dev_warn(dev, "Use slow word/byte SMBus read ops");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_byte;
+		dev_warn(dev, "Use slow byte SMBus read op");
+	} else /* no supported smbus read operations */ {
+		dev_err(dev, "No supported SMBus read op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Check i2c adapter write functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) {
+		pdev->smb_write = idt_smb_write_block;
+		dev_dbg(dev, "SMBus block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
+		pdev->smb_write = idt_smb_write_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_word;
+		dev_warn(dev, "Use slow word/byte SMBus write op");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_byte;
+		dev_warn(dev, "Use slow byte SMBus write op");
+	} else /* no supported smbus write operations */ {
+		dev_err(dev, "No supported SMBus write op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Initialize IDT SMBus slave interface mutex */
+	mutex_init(&pdev->smb_mtx);
+
+	dev_dbg(dev, "SMBus functionality successfully checked");
+
+	return 0;
+}
+
+/*
+ * idt_check_dev() - check whether it's really IDT 89HPESx device
+ * @pdev:	Pointer to the driver data
+ * Return status of i2c adapter check operation
+ */
+static int idt_check_dev(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	u32 viddid;
+	int ret;
+
+	/* Read VID and DID directly from IDT memory space */
+	ret = idt_csr_read(pdev, IDT_VIDDID_CSR, &viddid);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read VID/DID");
+		return ret;
+	}
+
+	/* Check whether it's IDT device */
+	if ((viddid & IDT_VID_MASK) != PCI_VENDOR_ID_IDT) {
+		dev_err(dev, "Got unsupported VID/DID: 0x%08x", viddid);
+		return -ENODEV;
+	}
+
+	dev_info(dev, "Found IDT 89HPES device VID:0x%04x, DID:0x%04x",
+		(viddid & IDT_VID_MASK), (viddid >> 16));
+
+	return 0;
+}
+
+/*
+ * idt_create_sysfs_files() - create sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+static int idt_create_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	int ret;
+
+	/* Don't do anything if EEPROM isn't accessible */
+	if (pdev->eesize == 0) {
+		dev_dbg(dev, "Skip creating sysfs-files");
+		return 0;
+	}
+
+	/* Allocate memory for attribute file */
+	pdev->ee_file = devm_kmalloc(dev, sizeof(*pdev->ee_file), GFP_KERNEL);
+	if (!pdev->ee_file)
+		return -ENOMEM;
+
+	/* Copy the declared EEPROM attr structure to change some of fields */
+	memcpy(pdev->ee_file, &bin_attr_eeprom, sizeof(*pdev->ee_file));
+
+	/* In case of read-only EEPROM get rid of write ability */
+	if (pdev->eero) {
+		pdev->ee_file->attr.mode &= ~0200;
+		pdev->ee_file->write = NULL;
+	}
+	/* Create EEPROM sysfs file */
+	pdev->ee_file->size = pdev->eesize;
+	ret = sysfs_create_bin_file(&dev->kobj, pdev->ee_file);
+	if (ret != 0) {
+		kfree(pdev->ee_file);
+		dev_err(dev, "Failed to create EEPROM sysfs-node");
+		return ret;
+	}
+
+	dev_dbg(dev, "Sysfs-files created");
+
+	return 0;
+}
+
+/*
+ * idt_remove_sysfs_files() - remove sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	/* Don't do anything if EEPROM wasn't accessible */
+	if (pdev->eesize == 0)
+		return;
+
+	/* Remove EEPROM sysfs file */
+	sysfs_remove_bin_file(&dev->kobj, pdev->ee_file);
+
+	/* Free memory allocated for bin_attribute structure */
+	kfree(pdev->ee_file);
+
+	dev_dbg(dev, "Sysfs-files removed");
+}
+
+/*
+ * idt_create_dbgfs_files() - create debugfs files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+#define CSRNAME_LEN	((size_t)32)
+static int idt_create_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	struct i2c_client *cli = pdev->client;
+	char fname[CSRNAME_LEN];
+
+	/* Initialize basic value of CSR debugfs dentries */
+	pdev->csr_dir = NULL;
+	pdev->csr_file = NULL;
+
+	/* Return failure if root directory doesn't exist */
+	if (!csr_dbgdir) {
+		dev_dbg(dev, "No Debugfs root directory");
+		return -EINVAL;
+	}
+
+	/* Create Debugfs directory for CSR file */
+	snprintf(fname, CSRNAME_LEN, "%d-%04hx", cli->adapter->nr, cli->addr);
+	pdev->csr_dir = debugfs_create_dir(fname, csr_dbgdir);
+	if (IS_ERR_OR_NULL(pdev->csr_dir)) {
+		dev_err(dev, "Failed to create CSR node directory");
+		return -EINVAL;
+	}
+
+	/* Create Debugfs file for CSR read/write operations */
+	pdev->csr_file = debugfs_create_file(cli->name, 0600,
+		pdev->csr_dir, pdev, &csr_dbgfs_ops);
+	if (IS_ERR_OR_NULL(pdev->csr_file)) {
+		dev_err(dev, "Failed to create CSR dbgfs-node");
+		debugfs_remove_recursive(pdev->csr_dir);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "Debugfs-files created");
+
+	return 0;
+}
+
+/*
+ * idt_remove_dbgfs_files() - remove debugfs files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	/* Remove CSR directory and it sysfs-node */
+	debugfs_remove_recursive(pdev->csr_dir);
+
+	dev_dbg(&pdev->client->dev, "Debugfs-files removed");
+}
+
+/*
+ * idt_probe() - IDT 89HPESx driver probe() callback method
+ */
+static int idt_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Create driver data */
+	pdev = idt_create_pdev(client);
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	/* Set SMBus operations */
+	ret = idt_set_smbus_ops(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Check whether it is truly IDT 89HPESx device */
+	ret = idt_check_dev(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create sysfs files */
+	ret = idt_create_sysfs_files(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create debugfs files */
+	(void)idt_create_dbgfs_files(pdev);
+
+	dev_dbg(&client->dev, "IDT %s device probed", id->name);
+
+	return 0;
+
+err_free_pdev:
+	idt_free_pdev(pdev);
+
+	return ret;
+}
+
+/*
+ * idt_remove() - IDT 89HPESx driver remove() callback method
+ */
+static int idt_remove(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev = i2c_get_clientdata(client);
+
+	/* Remove debugfs files first */
+	idt_remove_dbgfs_files(pdev);
+
+	/* Remove sysfs files */
+	idt_remove_sysfs_files(pdev);
+
+	/* Discard driver data structure */
+	idt_free_pdev(pdev);
+
+	dev_dbg(&client->dev, "IDT 89HPESx device removed");
+
+	return 0;
+}
+
+/*
+ * ee_ids - array of supported EEPROMs
+ */
+static const struct i2c_device_id ee_ids[] = {
+	{ "24c32",  4096},
+	{ "24c64",  8192},
+	{ "24c128", 16384},
+	{ "24c256", 32768},
+	{ "24c512", 65536},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, ee_ids);
+
+/*
+ * idt_ids - supported IDT 89HPESx devices
+ */
+static const struct i2c_device_id idt_ids[] = {
+	{ "89hpes8nt2", 0 },
+	{ "89hpes12nt3", 0 },
+
+	{ "89hpes24nt6ag2", 0 },
+	{ "89hpes32nt8ag2", 0 },
+	{ "89hpes32nt8bg2", 0 },
+	{ "89hpes12nt12g2", 0 },
+	{ "89hpes16nt16g2", 0 },
+	{ "89hpes24nt24g2", 0 },
+	{ "89hpes32nt24ag2", 0 },
+	{ "89hpes32nt24bg2", 0 },
+
+	{ "89hpes12n3", 0 },
+	{ "89hpes12n3a", 0 },
+	{ "89hpes24n3", 0 },
+	{ "89hpes24n3a", 0 },
+
+	{ "89hpes32h8", 0 },
+	{ "89hpes32h8g2", 0 },
+	{ "89hpes48h12", 0 },
+	{ "89hpes48h12g2", 0 },
+	{ "89hpes48h12ag2", 0 },
+	{ "89hpes16h16", 0 },
+	{ "89hpes22h16", 0 },
+	{ "89hpes22h16g2", 0 },
+	{ "89hpes34h16", 0 },
+	{ "89hpes34h16g2", 0 },
+	{ "89hpes64h16", 0 },
+	{ "89hpes64h16g2", 0 },
+	{ "89hpes64h16ag2", 0 },
+
+	/* { "89hpes3t3", 0 }, // No SMBus-slave iface */
+	{ "89hpes12t3g2", 0 },
+	{ "89hpes24t3g2", 0 },
+	/* { "89hpes4t4", 0 }, // No SMBus-slave iface */
+	{ "89hpes16t4", 0 },
+	{ "89hpes4t4g2", 0 },
+	{ "89hpes10t4g2", 0 },
+	{ "89hpes16t4g2", 0 },
+	{ "89hpes16t4ag2", 0 },
+	{ "89hpes5t5", 0 },
+	{ "89hpes6t5", 0 },
+	{ "89hpes8t5", 0 },
+	{ "89hpes8t5a", 0 },
+	{ "89hpes24t6", 0 },
+	{ "89hpes6t6g2", 0 },
+	{ "89hpes24t6g2", 0 },
+	{ "89hpes16t7", 0 },
+	{ "89hpes32t8", 0 },
+	{ "89hpes32t8g2", 0 },
+	{ "89hpes48t12", 0 },
+	{ "89hpes48t12g2", 0 },
+	{ /* END OF LIST */ }
+};
+MODULE_DEVICE_TABLE(i2c, idt_ids);
+
+/*
+ * idt_driver - IDT 89HPESx driver structure
+ */
+static struct i2c_driver idt_driver = {
+	.driver = {
+		.name = IDT_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = idt_probe,
+	.remove = idt_remove,
+	.id_table = idt_ids,
+};
+
+/*
+ * idt_init() - IDT 89HPESx driver init() callback method
+ */
+static int __init idt_init(void)
+{
+	/* Create Debugfs directory first */
+	if (debugfs_initialized())
+		csr_dbgdir = debugfs_create_dir("idt_csr", NULL);
+
+	/* Add new i2c-device driver */
+	return i2c_add_driver(&idt_driver);
+}
+module_init(idt_init);
+
+/*
+ * idt_exit() - IDT 89HPESx driver exit() callback method
+ */
+static void __exit idt_exit(void)
+{
+	/* Discard debugfs directory and all files if any */
+	debugfs_remove_recursive(csr_dbgdir);
+
+	/* Unregister i2c-device driver */
+	i2c_del_driver(&idt_driver);
+}
+module_exit(idt_exit);
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v4 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2016-12-13 14:22     ` [PATCH v4 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2016-12-13 14:22       ` [PATCH v4 1/2] " Serge Semin
@ 2016-12-13 14:22       ` Serge Semin
  2017-01-13 12:16       ` [PATCH v5 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2 siblings, 0 replies; 34+ messages in thread
From: Serge Semin @ 2016-12-13 14:22 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

IDT 89HPESx PCIe-switches exposes SMBus interface to have an access to
the device CSRs and EEPROM. So to properly utilize the interface
functionality, developer should declare a valid dts-file node, which
would refer to the corresponding 89HPESx device.

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
 .../devicetree/bindings/misc/idt_89hpesx.txt       | 44 ++++++++++++++++++++++
 1 file changed, 44 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt

diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
new file mode 100644
index 0000000..b9093b7
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
@@ -0,0 +1,44 @@
+EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
+
+Required properties:
+  - compatible : should be "<manufacturer>,<type>"
+		 Basically there is only one manufacturer: idt, but some
+		 compatible devices may be produced in future. Following devices
+		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
+		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
+		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
+		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
+		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
+		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
+		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
+		 89hpes64h16ag2;
+		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
+		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
+		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
+		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
+		 89hpes48t12, 89hpes48t12g2.
+  - reg :	 I2C address of the IDT 89HPESx device.
+
+Optionally there can be EEPROM-compatible subnode:
+  - compatible:  There are five EEPROM devices supported: 24c32, 24c64, 24c128,
+		 24c256 and 24c512 differed by size.
+  - reg:         Custom address of EEPROM device (If not specified IDT 89HPESx
+    (optional)	 device will try to communicate with EEPROM sited by default
+		 address - 0x50)
+  - read-only :	 Parameterless property disables writes to the EEPROM
+    (optional)
+
+Example:
+	idt@60 {
+		compatible = "idt,89hpes32nt8ag2";
+		reg = <0x74>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		eeprom@50 {
+			compatible = "onsemi,24c64";
+			reg = <0x50>;
+			read-only;
+		};
+	};
+
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH v4 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-12-13 14:22       ` [PATCH v4 1/2] " Serge Semin
@ 2017-01-11  8:21         ` Greg KH
  2017-01-12 22:54           ` Serge Semin
  0 siblings, 1 reply; 34+ messages in thread
From: Greg KH @ 2017-01-11  8:21 UTC (permalink / raw)
  To: Serge Semin
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Tue, Dec 13, 2016 at 05:22:50PM +0300, Serge Semin wrote:
> +struct idt_89hpesx_dev {
> +	u32 eesize;
> +	bool eero;
> +	u8 eeaddr;
> +
> +	u8 inieecmd;
> +	u8 inicsrcmd;
> +	u8 iniccode;
> +
> +	atomic_t csr;

Why is this an atomic_t and not just a "normal" u32 or u64?  I don't see
the need for an atomic variable at all here, do you?


> +
> +	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
> +	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
> +	struct mutex smb_mtx;
> +
> +	struct i2c_client *client;
> +
> +	struct bin_attribute *ee_file;
> +	struct dentry *csr_dir;
> +	struct dentry *csr_file;
> +};

<snip>

> +
> +static int idt_create_dbgfs_files(struct idt_89hpesx_dev *pdev)
> +{
> +	struct device *dev = &pdev->client->dev;
> +	struct i2c_client *cli = pdev->client;
> +	char fname[CSRNAME_LEN];
> +
> +	/* Initialize basic value of CSR debugfs dentries */
> +	pdev->csr_dir = NULL;
> +	pdev->csr_file = NULL;
> +
> +	/* Return failure if root directory doesn't exist */
> +	if (!csr_dbgdir) {
> +		dev_dbg(dev, "No Debugfs root directory");
> +		return -EINVAL;
> +	}

If debugfs is not enabled, don't error out, just keep going, it should
never stop kernel code from running properly.

Also, this test isn't really doing what you think it is doing...

> +	/* Create Debugfs directory for CSR file */
> +	snprintf(fname, CSRNAME_LEN, "%d-%04hx", cli->adapter->nr, cli->addr);
> +	pdev->csr_dir = debugfs_create_dir(fname, csr_dbgdir);
> +	if (IS_ERR_OR_NULL(pdev->csr_dir)) {
> +		dev_err(dev, "Failed to create CSR node directory");
> +		return -EINVAL;

Again, don't do this, you really don't care if debugfs worked or not.

> +	}
> +
> +	/* Create Debugfs file for CSR read/write operations */
> +	pdev->csr_file = debugfs_create_file(cli->name, 0600,
> +		pdev->csr_dir, pdev, &csr_dbgfs_ops);
> +	if (IS_ERR_OR_NULL(pdev->csr_file)) {
> +		dev_err(dev, "Failed to create CSR dbgfs-node");
> +		debugfs_remove_recursive(pdev->csr_dir);
> +		return -EINVAL;

Same here, just create the file and move on.

> +	}
> +
> +	dev_dbg(dev, "Debugfs-files created");

You do know about ftrace, right?  Please remove all of these
"trace-like" debugging lines, they aren't needed for anyone.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v4 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2017-01-11  8:21         ` Greg KH
@ 2017-01-12 22:54           ` Serge Semin
  2017-01-13  7:22             ` Greg KH
  0 siblings, 1 reply; 34+ messages in thread
From: Serge Semin @ 2017-01-12 22:54 UTC (permalink / raw)
  To: Greg KH
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Wed, Jan 11, 2017 at 09:21:19AM +0100, Greg KH <gregkh@linuxfoundation.org> wrote:
> On Tue, Dec 13, 2016 at 05:22:50PM +0300, Serge Semin wrote:
> > +struct idt_89hpesx_dev {
> > +	u32 eesize;
> > +	bool eero;
> > +	u8 eeaddr;
> > +
> > +	u8 inieecmd;
> > +	u8 inicsrcmd;
> > +	u8 iniccode;
> > +
> > +	atomic_t csr;
> 
> Why is this an atomic_t and not just a "normal" u32 or u64?  I don't see
> the need for an atomic variable at all here, do you?
> 

Of course, I did. Since it was shared resource before it was necessary to have
it atomically accessed. But since we moved "csr" sysfs node to DebugFS, it might not
be necessary of atomic_t type.
Ok, I'll make it unsigned int.

> 
> > +
> > +	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
> > +	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
> > +	struct mutex smb_mtx;
> > +
> > +	struct i2c_client *client;
> > +
> > +	struct bin_attribute *ee_file;
> > +	struct dentry *csr_dir;
> > +	struct dentry *csr_file;
> > +};
> 
> <snip>
> 
> > +
> > +static int idt_create_dbgfs_files(struct idt_89hpesx_dev *pdev)
> > +{
> > +	struct device *dev = &pdev->client->dev;
> > +	struct i2c_client *cli = pdev->client;
> > +	char fname[CSRNAME_LEN];
> > +
> > +	/* Initialize basic value of CSR debugfs dentries */
> > +	pdev->csr_dir = NULL;
> > +	pdev->csr_file = NULL;
> > +
> > +	/* Return failure if root directory doesn't exist */
> > +	if (!csr_dbgdir) {
> > +		dev_dbg(dev, "No Debugfs root directory");
> > +		return -EINVAL;
> > +	}
> 
> If debugfs is not enabled, don't error out, just keep going, it should
> never stop kernel code from running properly.
> 
> Also, this test isn't really doing what you think it is doing...
> 

I see, it must be replaced with IS_ERR_OR_NULL() test. But I don't think,
it would be good to get rid of dev_dbg() completely here. In case if
debugging is enabled, user would understand why csr-node isn't created within
DebugFS directory. I don't see the reasoning why one shouldn't know a source
of possible problems.
(See the next comment as continue of the discussion)

> > +	/* Create Debugfs directory for CSR file */
> > +	snprintf(fname, CSRNAME_LEN, "%d-%04hx", cli->adapter->nr, cli->addr);
> > +	pdev->csr_dir = debugfs_create_dir(fname, csr_dbgdir);
> > +	if (IS_ERR_OR_NULL(pdev->csr_dir)) {
> > +		dev_err(dev, "Failed to create CSR node directory");
> > +		return -EINVAL;
> 
> Again, don't do this, you really don't care if debugfs worked or not.
> 

Actually the driver doesn't stop the kernel code from running, if it finds out
any problem with DebugFS CSR-node creation. The function just logs the error
and return error status. Take a look the place the method is called:
1489        /* Create debugfs files */
1490        (void)idt_create_dbgfs_files(pdev);
The initialization code doesn't check the return value at all, so the driver
will proceed with further code.
Why did I make the function with return value? Because it's a good style to
always return a status of function code execution if it may fail, but only
caller will decide whether to check the return value or not.

Regarding the error printing. In case if the code gets to this check, one can
be sure the DebugFS works properly, so in case if the driver failed to create
the corresponding sub-directory or node, it is really error to have any failure
at this point, and a user should be notified. But still the driver won't stop
functioning, since the caller doesn't check the return value.

Hopefully you'll understand my point.

> > +	}
> > +
> > +	/* Create Debugfs file for CSR read/write operations */
> > +	pdev->csr_file = debugfs_create_file(cli->name, 0600,
> > +		pdev->csr_dir, pdev, &csr_dbgfs_ops);
> > +	if (IS_ERR_OR_NULL(pdev->csr_file)) {
> > +		dev_err(dev, "Failed to create CSR dbgfs-node");
> > +		debugfs_remove_recursive(pdev->csr_dir);
> > +		return -EINVAL;
> 
> Same here, just create the file and move on.
> 
> > +	}
> > +
> > +	dev_dbg(dev, "Debugfs-files created");
> 
> You do know about ftrace, right?  Please remove all of these
> "trace-like" debugging lines, they aren't needed for anyone.
> 

Ok, I'll remove all these prints, even though I do find these prints being
handy to have initialization process printed on debugging stage.

> thanks,
> 
> greg k-h

thanks,
Sergey

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v4 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2017-01-12 22:54           ` Serge Semin
@ 2017-01-13  7:22             ` Greg KH
  2017-01-13  9:47               ` Serge Semin
  0 siblings, 1 reply; 34+ messages in thread
From: Greg KH @ 2017-01-13  7:22 UTC (permalink / raw)
  To: Serge Semin
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Fri, Jan 13, 2017 at 01:54:17AM +0300, Serge Semin wrote:
> On Wed, Jan 11, 2017 at 09:21:19AM +0100, Greg KH <gregkh@linuxfoundation.org> wrote:
> > > +	/* Return failure if root directory doesn't exist */
> > > +	if (!csr_dbgdir) {
> > > +		dev_dbg(dev, "No Debugfs root directory");
> > > +		return -EINVAL;
> > > +	}
> > 
> > If debugfs is not enabled, don't error out, just keep going, it should
> > never stop kernel code from running properly.
> > 
> > Also, this test isn't really doing what you think it is doing...
> > 
> 
> I see, it must be replaced with IS_ERR_OR_NULL() test.

No!  That's a pain, when the debugfs interface was created its goal was
to make it _easy_ to use, not hard.  IS_ERR_OR_NULL() is hard, and
messy, don't do that.

> But I don't think,
> it would be good to get rid of dev_dbg() completely here. In case if
> debugging is enabled, user would understand why csr-node isn't created within
> DebugFS directory. I don't see the reasoning why one shouldn't know a source
> of possible problems.
> (See the next comment as continue of the discussion)

Why would a user care about debugfs?

> > > +	/* Create Debugfs directory for CSR file */
> > > +	snprintf(fname, CSRNAME_LEN, "%d-%04hx", cli->adapter->nr, cli->addr);
> > > +	pdev->csr_dir = debugfs_create_dir(fname, csr_dbgdir);
> > > +	if (IS_ERR_OR_NULL(pdev->csr_dir)) {
> > > +		dev_err(dev, "Failed to create CSR node directory");
> > > +		return -EINVAL;
> > 
> > Again, don't do this, you really don't care if debugfs worked or not.
> > 
> 
> Actually the driver doesn't stop the kernel code from running, if it finds out
> any problem with DebugFS CSR-node creation. The function just logs the error
> and return error status. Take a look the place the method is called:
> 1489        /* Create debugfs files */
> 1490        (void)idt_create_dbgfs_files(pdev);
> The initialization code doesn't check the return value at all, so the driver
> will proceed with further code.
> Why did I make the function with return value? Because it's a good style to
> always return a status of function code execution if it may fail, but only
> caller will decide whether to check the return value or not.

There is only one type of error that a debugfs call can return, and that
is if debugfs is not enabled in the build.  That's it, you don't need to
care about any of that.

> Regarding the error printing. In case if the code gets to this check, one can
> be sure the DebugFS works properly, so in case if the driver failed to create
> the corresponding sub-directory or node, it is really error to have any failure
> at this point, and a user should be notified. But still the driver won't stop
> functioning, since the caller doesn't check the return value.
> 
> Hopefully you'll understand my point.

Please understand mine, debugfs is supposed to be easy to use, you are
not testing things properly here, and when you are, it doesn't matter.
Just call the functions, save the return results if you need to (for
dentries and the like), and move on.  No error handling needed AT ALL!

Yes, it feels "odd" for kernel code, but remember, this is only for
debugging.  Your code should not have any different codepaths for if the
debugging logic worked or not.  It doesn't care at all.  So please, make
it simple.

> > > +	dev_dbg(dev, "Debugfs-files created");
> > 
> > You do know about ftrace, right?  Please remove all of these
> > "trace-like" debugging lines, they aren't needed for anyone.
> > 
> 
> Ok, I'll remove all these prints, even though I do find these prints being
> handy to have initialization process printed on debugging stage.

Then use ftrace, that is what it is there for, don't roll your own
driver-specific-functionality please.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v4 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2017-01-13  7:22             ` Greg KH
@ 2017-01-13  9:47               ` Serge Semin
  0 siblings, 0 replies; 34+ messages in thread
From: Serge Semin @ 2017-01-13  9:47 UTC (permalink / raw)
  To: Greg KH
  Cc: srinivas.kandagatla, andrew, robh+dt, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Fri, Jan 13, 2017 at 08:22:35AM +0100, Greg KH <gregkh@linuxfoundation.org> wrote:
> On Fri, Jan 13, 2017 at 01:54:17AM +0300, Serge Semin wrote:
> > On Wed, Jan 11, 2017 at 09:21:19AM +0100, Greg KH <gregkh@linuxfoundation.org> wrote:
> > > > +	/* Return failure if root directory doesn't exist */
> > > > +	if (!csr_dbgdir) {
> > > > +		dev_dbg(dev, "No Debugfs root directory");
> > > > +		return -EINVAL;
> > > > +	}
> > > 
> > > If debugfs is not enabled, don't error out, just keep going, it should
> > > never stop kernel code from running properly.
> > > 
> > > Also, this test isn't really doing what you think it is doing...
> > > 
> > 
> > I see, it must be replaced with IS_ERR_OR_NULL() test.
> 
> No!  That's a pain, when the debugfs interface was created its goal was
> to make it _easy_ to use, not hard.  IS_ERR_OR_NULL() is hard, and
> messy, don't do that.
> 
> > But I don't think,
> > it would be good to get rid of dev_dbg() completely here. In case if
> > debugging is enabled, user would understand why csr-node isn't created within
> > DebugFS directory. I don't see the reasoning why one shouldn't know a source
> > of possible problems.
> > (See the next comment as continue of the discussion)
> 
> Why would a user care about debugfs?
> 
> > > > +	/* Create Debugfs directory for CSR file */
> > > > +	snprintf(fname, CSRNAME_LEN, "%d-%04hx", cli->adapter->nr, cli->addr);
> > > > +	pdev->csr_dir = debugfs_create_dir(fname, csr_dbgdir);
> > > > +	if (IS_ERR_OR_NULL(pdev->csr_dir)) {
> > > > +		dev_err(dev, "Failed to create CSR node directory");
> > > > +		return -EINVAL;
> > > 
> > > Again, don't do this, you really don't care if debugfs worked or not.
> > > 
> > 
> > Actually the driver doesn't stop the kernel code from running, if it finds out
> > any problem with DebugFS CSR-node creation. The function just logs the error
> > and return error status. Take a look the place the method is called:
> > 1489        /* Create debugfs files */
> > 1490        (void)idt_create_dbgfs_files(pdev);
> > The initialization code doesn't check the return value at all, so the driver
> > will proceed with further code.
> > Why did I make the function with return value? Because it's a good style to
> > always return a status of function code execution if it may fail, but only
> > caller will decide whether to check the return value or not.
> 
> There is only one type of error that a debugfs call can return, and that
> is if debugfs is not enabled in the build.  That's it, you don't need to
> care about any of that.
> 
> > Regarding the error printing. In case if the code gets to this check, one can
> > be sure the DebugFS works properly, so in case if the driver failed to create
> > the corresponding sub-directory or node, it is really error to have any failure
> > at this point, and a user should be notified. But still the driver won't stop
> > functioning, since the caller doesn't check the return value.
> > 
> > Hopefully you'll understand my point.
> 
> Please understand mine, debugfs is supposed to be easy to use, you are
> not testing things properly here, and when you are, it doesn't matter.
> Just call the functions, save the return results if you need to (for
> dentries and the like), and move on.  No error handling needed AT ALL!
> 
> Yes, it feels "odd" for kernel code, but remember, this is only for
> debugging.  Your code should not have any different codepaths for if the
> debugging logic worked or not.  It doesn't care at all.  So please, make
> it simple.
> 
> > > > +	dev_dbg(dev, "Debugfs-files created");
> > > 
> > > You do know about ftrace, right?  Please remove all of these
> > > "trace-like" debugging lines, they aren't needed for anyone.
> > > 
> > 
> > Ok, I'll remove all these prints, even though I do find these prints being
> > handy to have initialization process printed on debugging stage.
> 
> Then use ftrace, that is what it is there for, don't roll your own
> driver-specific-functionality please.
> 
> thanks,
> 
> greg k-h

Ok, I see your point and do as you say.

Thanks,
Serge

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v5 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2016-12-13 14:22     ` [PATCH v4 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2016-12-13 14:22       ` [PATCH v4 1/2] " Serge Semin
  2016-12-13 14:22       ` [PATCH v4 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
@ 2017-01-13 12:16       ` Serge Semin
  2017-01-13 12:16         ` [PATCH v5 1/2] " Serge Semin
  2017-01-13 12:16         ` [PATCH v5 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
  2 siblings, 2 replies; 34+ messages in thread
From: Serge Semin @ 2017-01-13 12:16 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

Changelog v3:
- Get rid of dev_*_idt() macros
- Replace to_pdev_kobj() macro with naked dev_get_drvdata() call
- Return naked 0 instead of SUCCESS macro
- IDT CSR debug file is moved to debugfs
- BIN_ATTR_RW is used to declare sysfs binary attribute
- Moved bindings file to a separate patch
- Need to create a specific bin_attribute structure for each device
- Perform a few read retries with delays if EEPROM is busy

Changelog v4:
- Make 89HPESx device bindings to have one EEPROM subnode
- Alter 89HPESx device bindings text

Changelog v5:
- Replace atomic_t CSR field with of u16 type
- Get rid of dev_dbg() print outs of initialization methods
- Simplify DebugFS initialization method

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>

Serge Semin (2):
  eeprom: Add IDT 89HPESx EEPROM/CSR driver
  eeprom: Add IDT 89HPESx driver bindings file

 .../devicetree/bindings/misc/idt_89hpesx.txt       |   44 +
 drivers/misc/eeprom/Kconfig                        |   10 +
 drivers/misc/eeprom/Makefile                       |    1 +
 drivers/misc/eeprom/idt_89hpesx.c                  | 1587 ++++++++++++++++++++
 4 files changed, 1642 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

-- 
2.6.6

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v5 1/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver
  2017-01-13 12:16       ` [PATCH v5 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
@ 2017-01-13 12:16         ` Serge Semin
  2017-01-13 12:16         ` [PATCH v5 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
  1 sibling, 0 replies; 34+ messages in thread
From: Serge Semin @ 2017-01-13 12:16 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

  This driver provides an access to EEPROM of IDT PCIe-switches. IDT PCIe-
switches expose a simple SMBus interface to perform IO-operations from/to
EEPROM, which is located at private (so called Master) SMBus. The driver
creates a simple binary sysfs-file to have an access to the EEPROM using
the SMBus-slave interface in the i2c-device susfs-directory:
     /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
In case if read-only flag is specified at dts-node of the device, User-space
applications won't be able to write to the EEPROM sysfs-node.

  Additionally IDT 89HPESx SMBus interface has an ability to read/write
values of device CSRs. This driver exposes debugfs-file to perform simple
IO-operations using that ability for just basic debug purpose. Particularly
the next file is created in the specific debugfs-directory:
     /sys/kernel/debug/idt_csr/
Format of the debugfs-file value is:
     $ cat /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
     <CSR address>:<CSR value>
So reading the content of the file gives current CSR address and it value.
If User-space application wishes to change current CSR address, it can just
write a proper value to the sysfs-file:
     $ echo "<CSR address>" >
         /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>
If it wants to change the CSR value as well, the format of the write
operation is:
     $ echo "<CSR address>:<CSR value>" > \
         /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
CSR address and value can be any of hexadecimal, decimal or octal format.

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
 drivers/misc/eeprom/Kconfig       |   10 +
 drivers/misc/eeprom/Makefile      |    1 +
 drivers/misc/eeprom/idt_89hpesx.c | 1587 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1598 insertions(+)
 create mode 100644 drivers/misc/eeprom/idt_89hpesx.c

diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig
index c4e41c2..de58762 100644
--- a/drivers/misc/eeprom/Kconfig
+++ b/drivers/misc/eeprom/Kconfig
@@ -100,4 +100,14 @@ config EEPROM_DIGSY_MTC_CFG
 
 	  If unsure, say N.
 
+config EEPROM_IDT_89HPESX
+	tristate "IDT 89HPESx PCIe-swtiches EEPROM / CSR support"
+	depends on I2C && SYSFS
+	help
+	  Enable this driver to get read/write access to EEPROM / CSRs
+	  over IDT PCIe-swtich i2c-slave interface.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called idt_89hpesx.
+
 endmenu
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile
index fc1e81d..90a5262 100644
--- a/drivers/misc/eeprom/Makefile
+++ b/drivers/misc/eeprom/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o
 obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
 obj-$(CONFIG_EEPROM_93XX46)	+= eeprom_93xx46.o
 obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
+obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c
new file mode 100644
index 0000000..c53f0e5
--- /dev/null
+++ b/drivers/misc/eeprom/idt_89hpesx.c
@@ -0,0 +1,1587 @@
+/*
+ *   This file is provided under a GPLv2 license.  When using or
+ *   redistributing this file, you may do so under that license.
+ *
+ *   GPL LICENSE SUMMARY
+ *
+ *   Copyright (C) 2016 T-Platforms. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or modify it
+ *   under the terms and conditions of the GNU General Public License,
+ *   version 2, as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful, but WITHOUT
+ *   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ *   more details.
+ *
+ *   You should have received a copy of the GNU General Public License along
+ *   with this program; if not, it can be found <http://www.gnu.org/licenses/>.
+ *
+ *   The full GNU General Public License is included in this distribution in
+ *   the file called "COPYING".
+ *
+ *   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.
+ *
+ * IDT PCIe-switch NTB Linux driver
+ *
+ * Contact Information:
+ * Serge Semin <fancer.lancer@gmail.com>, <Sergey.Semin@t-platforms.ru>
+ */
+/*
+ *           NOTE of the IDT 89HPESx SMBus-slave interface driver
+ *    This driver primarily is developed to have an access to EEPROM device of
+ * IDT PCIe-switches. IDT provides a simple SMBus interface to perform IO-
+ * operations from/to EEPROM, which is located at private (so called Master)
+ * SMBus of switches. Using that interface this the driver creates a simple
+ * binary sysfs-file in the device directory:
+ * /sys/bus/i2c/devices/<bus>-<devaddr>/eeprom
+ * In case if read-only flag is specified in the dts-node of device desription,
+ * User-space applications won't be able to write to the EEPROM sysfs-node.
+ *    Additionally IDT 89HPESx SMBus interface has an ability to write/read
+ * data of device CSRs. This driver exposes debugf-file to perform simple IO
+ * operations using that ability for just basic debug purpose. Particularly
+ * next file is created in the specific debugfs-directory:
+ * /sys/kernel/debug/idt_csr/
+ * Format of the debugfs-node is:
+ * $ cat /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * <CSR address>:<CSR value>
+ * So reading the content of the file gives current CSR address and it value.
+ * If User-space application wishes to change current CSR address,
+ * it can just write a proper value to the sysfs-file:
+ * $ echo "<CSR address>" > /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>
+ * If it wants to change the CSR value as well, the format of the write
+ * operation is:
+ * $ echo "<CSR address>:<CSR value>" > \
+ *        /sys/kernel/debug/idt_csr/<bus>-<devaddr>/<devname>;
+ * CSR address and value can be any of hexadecimal, decimal or octal format.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/debugfs.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/pci_ids.h>
+#include <linux/delay.h>
+
+#define IDT_NAME		"89hpesx"
+#define IDT_89HPESX_DESC	"IDT 89HPESx SMBus-slave interface driver"
+#define IDT_89HPESX_VER		"1.0"
+
+MODULE_DESCRIPTION(IDT_89HPESX_DESC);
+MODULE_VERSION(IDT_89HPESX_VER);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("T-platforms");
+
+/*
+ * csr_dbgdir - CSR read/write operations Debugfs directory
+ */
+static struct dentry *csr_dbgdir;
+
+/*
+ * struct idt_89hpesx_dev - IDT 89HPESx device data structure
+ * @eesize:	Size of EEPROM in bytes (calculated from "idt,eecompatible")
+ * @eero:	EEPROM Read-only flag
+ * @eeaddr:	EEPROM custom address
+ *
+ * @inieecmd:	Initial cmd value for EEPROM read/write operations
+ * @inicsrcmd:	Initial cmd value for CSR read/write operations
+ * @iniccode:	Initialial command code value for IO-operations
+ *
+ * @csr:	CSR address to perform read operation
+ *
+ * @smb_write:	SMBus write method
+ * @smb_read:	SMBus read method
+ * @smb_mtx:	SMBus mutex
+ *
+ * @client:	i2c client used to perform IO operations
+ *
+ * @ee_file:	EEPROM read/write sysfs-file
+ * @csr_file:	CSR read/write debugfs-node
+ */
+struct idt_smb_seq;
+struct idt_89hpesx_dev {
+	u32 eesize;
+	bool eero;
+	u8 eeaddr;
+
+	u8 inieecmd;
+	u8 inicsrcmd;
+	u8 iniccode;
+
+	u16 csr;
+
+	int (*smb_write)(struct idt_89hpesx_dev *, const struct idt_smb_seq *);
+	int (*smb_read)(struct idt_89hpesx_dev *, struct idt_smb_seq *);
+	struct mutex smb_mtx;
+
+	struct i2c_client *client;
+
+	struct bin_attribute *ee_file;
+	struct dentry *csr_dir;
+	struct dentry *csr_file;
+};
+
+/*
+ * struct idt_smb_seq - sequence of data to be read/written from/to IDT 89HPESx
+ * @ccode:	SMBus command code
+ * @bytecnt:	Byte count of operation
+ * @data:	Data to by written
+ */
+struct idt_smb_seq {
+	u8 ccode;
+	u8 bytecnt;
+	u8 *data;
+};
+
+/*
+ * struct idt_eeprom_seq - sequence of data to be read/written from/to EEPROM
+ * @cmd:	Transaction CMD
+ * @eeaddr:	EEPROM custom address
+ * @memaddr:	Internal memory address of EEPROM
+ * @data:	Data to be written at the memory address
+ */
+struct idt_eeprom_seq {
+	u8 cmd;
+	u8 eeaddr;
+	u16 memaddr;
+	u8 data;
+} __packed;
+
+/*
+ * struct idt_csr_seq - sequence of data to be read/written from/to CSR
+ * @cmd:	Transaction CMD
+ * @csraddr:	Internal IDT device CSR address
+ * @data:	Data to be read/written from/to the CSR address
+ */
+struct idt_csr_seq {
+	u8 cmd;
+	u16 csraddr;
+	u32 data;
+} __packed;
+
+/*
+ * SMBus command code macros
+ * @CCODE_END:		Indicates the end of transaction
+ * @CCODE_START:	Indicates the start of transaction
+ * @CCODE_CSR:		CSR read/write transaction
+ * @CCODE_EEPROM:	EEPROM read/write transaction
+ * @CCODE_BYTE:		Supplied data has BYTE length
+ * @CCODE_WORD:		Supplied data has WORD length
+ * @CCODE_BLOCK:	Supplied data has variable length passed in bytecnt
+ *			byte right following CCODE byte
+ */
+#define CCODE_END	((u8)0x01)
+#define CCODE_START	((u8)0x02)
+#define CCODE_CSR	((u8)0x00)
+#define CCODE_EEPROM	((u8)0x04)
+#define CCODE_BYTE	((u8)0x00)
+#define CCODE_WORD	((u8)0x20)
+#define CCODE_BLOCK	((u8)0x40)
+#define CCODE_PEC	((u8)0x80)
+
+/*
+ * EEPROM command macros
+ * @EEPROM_OP_WRITE:	EEPROM write operation
+ * @EEPROM_OP_READ:	EEPROM read operation
+ * @EEPROM_USA:		Use specified address of EEPROM
+ * @EEPROM_NAERR:	EEPROM device is not ready to respond
+ * @EEPROM_LAERR:	EEPROM arbitration loss error
+ * @EEPROM_MSS:		EEPROM misplace start & stop bits error
+ * @EEPROM_WR_CNT:	Bytes count to perform write operation
+ * @EEPROM_WRRD_CNT:	Bytes count to write before reading
+ * @EEPROM_RD_CNT:	Bytes count to perform read operation
+ * @EEPROM_DEF_SIZE:	Fall back size of EEPROM
+ * @EEPROM_DEF_ADDR:	Defatul EEPROM address
+ * @EEPROM_TOUT:	Timeout before retry read operation if eeprom is busy
+ */
+#define EEPROM_OP_WRITE	((u8)0x00)
+#define EEPROM_OP_READ	((u8)0x01)
+#define EEPROM_USA	((u8)0x02)
+#define EEPROM_NAERR	((u8)0x08)
+#define EEPROM_LAERR    ((u8)0x10)
+#define EEPROM_MSS	((u8)0x20)
+#define EEPROM_WR_CNT	((u8)5)
+#define EEPROM_WRRD_CNT	((u8)4)
+#define EEPROM_RD_CNT	((u8)5)
+#define EEPROM_DEF_SIZE	((u16)4096)
+#define EEPROM_DEF_ADDR	((u8)0x50)
+#define EEPROM_TOUT	(100)
+
+/*
+ * CSR command macros
+ * @CSR_DWE:		Enable all four bytes of the operation
+ * @CSR_OP_WRITE:	CSR write operation
+ * @CSR_OP_READ:	CSR read operation
+ * @CSR_RERR:		Read operation error
+ * @CSR_WERR:		Write operation error
+ * @CSR_WR_CNT:		Bytes count to perform write operation
+ * @CSR_WRRD_CNT:	Bytes count to write before reading
+ * @CSR_RD_CNT:		Bytes count to perform read operation
+ * @CSR_MAX:		Maximum CSR address
+ * @CSR_DEF:		Default CSR address
+ * @CSR_REAL_ADDR:	CSR real unshifted address
+ */
+#define CSR_DWE			((u8)0x0F)
+#define CSR_OP_WRITE		((u8)0x00)
+#define CSR_OP_READ		((u8)0x10)
+#define CSR_RERR		((u8)0x40)
+#define CSR_WERR		((u8)0x80)
+#define CSR_WR_CNT		((u8)7)
+#define CSR_WRRD_CNT		((u8)3)
+#define CSR_RD_CNT		((u8)7)
+#define CSR_MAX			((u32)0x3FFFF)
+#define CSR_DEF			((u16)0x0000)
+#define CSR_REAL_ADDR(val)	((unsigned int)val << 2)
+
+/*
+ * IDT 89HPESx basic register
+ * @IDT_VIDDID_CSR:	PCIe VID and DID of IDT 89HPESx
+ * @IDT_VID_MASK:	Mask of VID
+ */
+#define IDT_VIDDID_CSR	((u32)0x0000)
+#define IDT_VID_MASK	((u32)0xFFFF)
+
+/*
+ * IDT 89HPESx can send NACK when new command is sent before previous one
+ * fininshed execution. In this case driver retries operation
+ * certain times.
+ * @RETRY_CNT:		Number of retries before giving up and fail
+ * @idt_smb_safe:	Generate a retry loop on corresponding SMBus method
+ */
+#define RETRY_CNT (128)
+#define idt_smb_safe(ops, args...) ({ \
+	int __retry = RETRY_CNT; \
+	s32 __sts; \
+	do { \
+		__sts = i2c_smbus_ ## ops ## _data(args); \
+	} while (__retry-- && __sts < 0); \
+	__sts; \
+})
+
+/*===========================================================================
+ *                         i2c bus level IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_smb_write_byte() - SMBus write method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_byte(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied data sending byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Send data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_byte() - SMBus read method when I2C_SMBUS_BYTE_DATA operation
+ *                        is only available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_byte(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx;
+
+	/* Loop over the supplied buffer receiving byte one-by-one */
+	for (idx = 0; idx < seq->bytecnt; idx++) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == seq->bytecnt - 1)
+			ccode |= CCODE_END;
+
+		/* Read data from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_word() - SMBus write method when I2C_SMBUS_BYTE_DATA and
+ *                        I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_word(struct idt_89hpesx_dev *pdev,
+			      const struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data sending two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Send word data to the device */
+		sts = idt_smb_safe(write_word, pdev->client, ccode,
+			*(u16 *)&seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	/* If there is odd number of bytes then send just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Send byte data to the device */
+		sts = idt_smb_safe(write_byte, pdev->client, ccode,
+			seq->data[idx]);
+		if (sts != 0)
+			return (int)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_read_word() - SMBus read method when I2C_SMBUS_BYTE_DATA and
+ *                       I2C_FUNC_SMBUS_WORD_DATA operations are available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_word(struct idt_89hpesx_dev *pdev,
+			     struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+	int idx, evencnt;
+
+	/* Calculate the even count of data to send */
+	evencnt = seq->bytecnt - (seq->bytecnt % 2);
+
+	/* Loop over the supplied data reading two bytes at a time */
+	for (idx = 0; idx < evencnt; idx += 2) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_WORD;
+		if (idx == 0)
+			ccode |= CCODE_START;
+		if (idx == evencnt - 2)
+			ccode |= CCODE_END;
+
+		/* Read word data from the device */
+		sts = idt_smb_safe(read_word, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		*(u16 *)&seq->data[idx] = (u16)sts;
+	}
+
+	/* If there is odd number of bytes then receive just one last byte */
+	if (seq->bytecnt != evencnt) {
+		/* Collect the command code byte */
+		ccode = seq->ccode | CCODE_BYTE | CCODE_END;
+		if (idx == 0)
+			ccode |= CCODE_START;
+
+		/* Read last data byte from the device */
+		sts = idt_smb_safe(read_byte, pdev->client, ccode);
+		if (sts < 0)
+			return (int)sts;
+
+		seq->data[idx] = (u8)sts;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_block() - SMBus write method when I2C_SMBUS_BLOCK_DATA
+ *                         operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ */
+static int idt_smb_write_block(struct idt_89hpesx_dev *pdev,
+			       const struct idt_smb_seq *seq)
+{
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send block of data to the device */
+	return idt_smb_safe(write_block, pdev->client, ccode, seq->bytecnt,
+		seq->data);
+}
+
+/*
+ * idt_smb_read_block() - SMBus read method when I2C_SMBUS_BLOCK_DATA
+ *                        operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ */
+static int idt_smb_read_block(struct idt_89hpesx_dev *pdev,
+			      struct idt_smb_seq *seq)
+{
+	s32 sts;
+	u8 ccode;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read block of data from the device */
+	sts = idt_smb_safe(read_block, pdev->client, ccode, seq->data);
+	if (sts != seq->bytecnt)
+		return (sts < 0 ? sts : -ENODATA);
+
+	return 0;
+}
+
+/*
+ * idt_smb_write_i2c_block() - SMBus write method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                             operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Sequence of data to be written
+ *
+ * NOTE It's usual SMBus write block operation, except the actual data length is
+ * sent as first byte of data
+ */
+static int idt_smb_write_i2c_block(struct idt_89hpesx_dev *pdev,
+				   const struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the data to send. Length byte must be added prior the data */
+	buf[0] = seq->bytecnt;
+	memcpy(&buf[1], seq->data, seq->bytecnt);
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Send length and block of data to the device */
+	return idt_smb_safe(write_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+}
+
+/*
+ * idt_smb_read_i2c_block() - SMBus read method when I2C_SMBUS_I2C_BLOCK_DATA
+ *                            operation is available
+ * @pdev:	Pointer to the driver data
+ * @seq:	Buffer to read data to
+ *
+ * NOTE It's usual SMBus read block operation, except the actual data length is
+ * retrieved as first byte of data
+ */
+static int idt_smb_read_i2c_block(struct idt_89hpesx_dev *pdev,
+				  struct idt_smb_seq *seq)
+{
+	u8 ccode, buf[I2C_SMBUS_BLOCK_MAX + 1];
+	s32 sts;
+
+	/* Return error if too much data passed to send */
+	if (seq->bytecnt > I2C_SMBUS_BLOCK_MAX)
+		return -EINVAL;
+
+	/* Collect the command code byte */
+	ccode = seq->ccode | CCODE_BLOCK | CCODE_START | CCODE_END;
+
+	/* Read length and block of data from the device */
+	sts = idt_smb_safe(read_i2c_block, pdev->client, ccode,
+		seq->bytecnt + 1, buf);
+	if (sts != seq->bytecnt + 1)
+		return (sts < 0 ? sts : -ENODATA);
+	if (buf[0] != seq->bytecnt)
+		return -ENODATA;
+
+	/* Copy retrieved data to the output data buffer */
+	memcpy(seq->data, &buf[1], seq->bytecnt);
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          EEPROM IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_eeprom_read_byte() - read just one byte from EEPROM
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_read_byte(struct idt_89hpesx_dev *pdev, u16 memaddr,
+				u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret, retry;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/*
+	 * Sometimes EEPROM may respond with NACK if it's busy with previous
+	 * operation, so we need to perform a few attempts of read cycle
+	 */
+	retry = RETRY_CNT;
+	do {
+		/* Send EEPROM memory address to read data from */
+		smbseq.bytecnt = EEPROM_WRRD_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_READ;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to init eeprom addr 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Perform read operation */
+		smbseq.bytecnt = EEPROM_RD_CNT;
+		ret = pdev->smb_read(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev, "Failed to read eeprom data 0x%02hhx",
+				memaddr);
+			break;
+		}
+
+		/* Restart read operation if the device is busy */
+		if (retry && (eeseq.cmd & EEPROM_NAERR)) {
+			dev_dbg(dev, "EEPROM busy, retry reading after %d ms",
+				EEPROM_TOUT);
+			msleep(EEPROM_TOUT);
+			continue;
+		}
+
+		/* Check whether IDT successfully read data from EEPROM */
+		if (eeseq.cmd & (EEPROM_NAERR | EEPROM_LAERR | EEPROM_MSS)) {
+			dev_err(dev,
+				"Communication with eeprom failed, cmd 0x%hhx",
+				eeseq.cmd);
+			ret = -EREMOTEIO;
+			break;
+		}
+
+		/* Save retrieved data and exit the loop */
+		*data = eeseq.data;
+		break;
+	} while (retry--);
+
+	/* Return the status of operation */
+	return ret;
+}
+
+/*
+ * idt_eeprom_write() - EEPROM write operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to be written
+ * @data:	Data to be written to EEPROM
+ */
+static int idt_eeprom_write(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			    const u8 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_eeprom_seq eeseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+	u16 idx;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_EEPROM;
+	smbseq.data = (u8 *)&eeseq;
+
+	/* Send data byte-by-byte, checking if it is successfully written */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Perform write operation */
+		smbseq.bytecnt = EEPROM_WR_CNT;
+		eeseq.cmd = pdev->inieecmd | EEPROM_OP_WRITE;
+		eeseq.eeaddr = pdev->eeaddr;
+		eeseq.memaddr = cpu_to_le16(memaddr);
+		eeseq.data = data[idx];
+		ret = pdev->smb_write(pdev, &smbseq);
+		if (ret != 0) {
+			dev_err(dev,
+				"Failed to write 0x%04hx:0x%02hhx to eeprom",
+				memaddr, data[idx]);
+			goto err_mutex_unlock;
+		}
+
+		/*
+		 * Check whether the data is successfully written by reading
+		 * from the same EEPROM memory address.
+		 */
+		eeseq.data = ~data[idx];
+		ret = idt_eeprom_read_byte(pdev, memaddr, &eeseq.data);
+		if (ret != 0)
+			goto err_mutex_unlock;
+
+		/* Check whether the read byte is the same as written one */
+		if (eeseq.data != data[idx]) {
+			dev_err(dev, "Values don't match 0x%02hhx != 0x%02hhx",
+				eeseq.data, data[idx]);
+			ret = -EREMOTEIO;
+			goto err_mutex_unlock;
+		}
+
+		/* Unlock IDT SMBus device */
+err_mutex_unlock:
+		mutex_unlock(&pdev->smb_mtx);
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_eeprom_read() - EEPROM read operation
+ * @pdev:	Pointer to the driver data
+ * @memaddr:	Start EEPROM memory address
+ * @len:	Length of data to read
+ * @buf:	Buffer to read data to
+ */
+static int idt_eeprom_read(struct idt_89hpesx_dev *pdev, u16 memaddr, u16 len,
+			   u8 *buf)
+{
+	int ret;
+	u16 idx;
+
+	/* Read data byte-by-byte, retrying if it wasn't successful */
+	for (idx = 0; idx < len; idx++, memaddr++) {
+		/* Lock IDT SMBus device */
+		mutex_lock(&pdev->smb_mtx);
+
+		/* Just read the byte to the buffer */
+		ret = idt_eeprom_read_byte(pdev, memaddr, &buf[idx]);
+
+		/* Unlock IDT SMBus device */
+		mutex_unlock(&pdev->smb_mtx);
+
+		/* Return error if read operation failed */
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*===========================================================================
+ *                          CSR IO-operations
+ *===========================================================================
+ */
+
+/*
+ * idt_csr_write() - CSR write operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_write(struct idt_89hpesx_dev *pdev, u16 csraddr,
+			 const u32 data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Perform write operation */
+	smbseq.bytecnt = CSR_WR_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_WRITE;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	csrseq.data = cpu_to_le32(data);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write 0x%04x: 0x%04x to csr",
+			CSR_REAL_ADDR(csraddr), data);
+		goto err_mutex_unlock;
+	}
+
+	/* Send CSR address to read data from */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*
+ * idt_csr_read() - CSR read operation
+ * @pdev:	Pointer to the driver data
+ * @csraddr:	CSR address (with no two LS bits)
+ * @data:	Data to be written to CSR
+ */
+static int idt_csr_read(struct idt_89hpesx_dev *pdev, u16 csraddr, u32 *data)
+{
+	struct device *dev = &pdev->client->dev;
+	struct idt_csr_seq csrseq;
+	struct idt_smb_seq smbseq;
+	int ret;
+
+	/* Initialize SMBus sequence fields */
+	smbseq.ccode = pdev->iniccode | CCODE_CSR;
+	smbseq.data = (u8 *)&csrseq;
+
+	/* Lock IDT SMBus device */
+	mutex_lock(&pdev->smb_mtx);
+
+	/* Send CSR register address before reading it */
+	smbseq.bytecnt = CSR_WRRD_CNT;
+	csrseq.cmd = pdev->inicsrcmd | CSR_OP_READ;
+	csrseq.csraddr = cpu_to_le16(csraddr);
+	ret = pdev->smb_write(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to init csr address 0x%04x",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Perform read operation */
+	smbseq.bytecnt = CSR_RD_CNT;
+	ret = pdev->smb_read(pdev, &smbseq);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read csr 0x%04hx",
+			CSR_REAL_ADDR(csraddr));
+		goto err_mutex_unlock;
+	}
+
+	/* Check whether IDT successfully retrieved CSR data */
+	if (csrseq.cmd & (CSR_RERR | CSR_WERR)) {
+		dev_err(dev, "IDT failed to perform CSR r/w");
+		ret = -EREMOTEIO;
+		goto err_mutex_unlock;
+	}
+
+	/* Save data retrieved from IDT */
+	*data = le32_to_cpu(csrseq.data);
+
+	/* Unlock IDT SMBus device */
+err_mutex_unlock:
+	mutex_unlock(&pdev->smb_mtx);
+
+	return ret;
+}
+
+/*===========================================================================
+ *                          Sysfs/debugfs-nodes IO-operations
+ *===========================================================================
+ */
+
+/*
+ * eeprom_write() - EEPROM sysfs-node write callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t eeprom_write(struct file *filp, struct kobject *kobj,
+			    struct bin_attribute *attr,
+			    char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(kobj_to_dev(kobj));
+
+	/* Perform EEPROM write operation */
+	ret = idt_eeprom_write(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * eeprom_read() - EEPROM sysfs-node read callback
+ * @filep:	Pointer to the file system node
+ * @kobj:	Pointer to the kernel object related to the sysfs-node
+ * @attr:	Attributes of the file
+ * @buf:	Buffer to write data to
+ * @off:	Offset at which data should be written to
+ * @count:	Number of bytes to write
+ */
+static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
+			   struct bin_attribute *attr,
+			   char *buf, loff_t off, size_t count)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Retrieve driver data */
+	pdev = dev_get_drvdata(kobj_to_dev(kobj));
+
+	/* Perform EEPROM read operation */
+	ret = idt_eeprom_read(pdev, (u16)off, (u16)count, (u8 *)buf);
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_write() - CSR debugfs-node write callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to read data from
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It accepts either "0x<reg addr>:0x<value>" for saving register address
+ * and writing value to specified DWORD register or "0x<reg addr>" for
+ * just saving register address in order to perform next read operation.
+ *
+ * WARNING No spaces are allowed. Incoming string must be strictly formated as:
+ * "<reg addr>:<value>". Register address must be aligned within 4 bytes
+ * (one DWORD).
+ */
+static ssize_t idt_dbgfs_csr_write(struct file *filep, const char __user *ubuf,
+				   size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	char *colon_ch, *csraddr_str, *csrval_str;
+	int ret, csraddr_len, csrval_len;
+	u32 csraddr, csrval;
+	char *buf;
+
+	/* Copy data from User-space */
+	buf = kmalloc(count + 1, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = simple_write_to_buffer(buf, count, offp, ubuf, count);
+	if (ret < 0)
+		goto free_buf;
+	buf[count] = 0;
+
+	/* Find position of colon in the buffer */
+	colon_ch = strnchr(buf, count, ':');
+
+	/*
+	 * If there is colon passed then new CSR value should be parsed as
+	 * well, so allocate buffer for CSR address substring.
+	 * If no colon is found, then string must have just one number with
+	 * no new CSR value
+	 */
+	if (colon_ch != NULL) {
+		csraddr_len = colon_ch - buf;
+		csraddr_str =
+			kmalloc(sizeof(char)*(csraddr_len + 1), GFP_KERNEL);
+		if (csraddr_str == NULL)
+			return -ENOMEM;
+		/* Copy the register address to the substring buffer */
+		strncpy(csraddr_str, buf, csraddr_len);
+		csraddr_str[csraddr_len] = '\0';
+		/* Register value must follow the colon */
+		csrval_str = colon_ch + 1;
+		csrval_len = strnlen(csrval_str, count - csraddr_len);
+	} else /* if (str_colon == NULL) */ {
+		csraddr_str = (char *)buf; /* Just to shut warning up */
+		csraddr_len = strnlen(csraddr_str, count);
+		csrval_str = NULL;
+		csrval_len = 0;
+	}
+
+	/* Convert CSR address to u32 value */
+	ret = kstrtou32(csraddr_str, 0, &csraddr);
+	if (ret != 0)
+		goto free_csraddr_str;
+
+	/* Check whether passed register address is valid */
+	if (csraddr > CSR_MAX || !IS_ALIGNED(csraddr, SZ_4)) {
+		ret = -EINVAL;
+		goto free_csraddr_str;
+	}
+
+	/* Shift register address to the right so to have u16 address */
+	pdev->csr = (csraddr >> 2);
+
+	/* Parse new CSR value and send it to IDT, if colon has been found */
+	if (colon_ch != NULL) {
+		ret = kstrtou32(csrval_str, 0, &csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+
+		ret = idt_csr_write(pdev, pdev->csr, csrval);
+		if (ret != 0)
+			goto free_csraddr_str;
+	}
+
+	/* Free memory only if colon has been found */
+free_csraddr_str:
+	if (colon_ch != NULL)
+		kfree(csraddr_str);
+
+	/* Free buffer allocated for data retrieved from User-space */
+free_buf:
+	kfree(buf);
+
+	return (ret != 0 ? ret : count);
+}
+
+/*
+ * idt_dbgfs_csr_read() - CSR debugfs-node read callback
+ * @filep:	Pointer to the file system file descriptor
+ * @buf:	Buffer to write data to
+ * @count:	Size of the buffer
+ * @offp:	Offset within the file
+ *
+ * It just prints the pair "0x<reg addr>:0x<value>" to passed buffer.
+ */
+#define CSRBUF_SIZE	((size_t)32)
+static ssize_t idt_dbgfs_csr_read(struct file *filep, char __user *ubuf,
+				  size_t count, loff_t *offp)
+{
+	struct idt_89hpesx_dev *pdev = filep->private_data;
+	u32 csraddr, csrval;
+	char buf[CSRBUF_SIZE];
+	int ret, size;
+
+	/* Perform CSR read operation */
+	ret = idt_csr_read(pdev, pdev->csr, &csrval);
+	if (ret != 0)
+		return ret;
+
+	/* Shift register address to the left so to have real address */
+	csraddr = ((u32)pdev->csr << 2);
+
+	/* Print the "0x<reg addr>:0x<value>" to buffer */
+	size = snprintf(buf, CSRBUF_SIZE, "0x%05x:0x%08x\n",
+		(unsigned int)csraddr, (unsigned int)csrval);
+
+	/* Copy data to User-space */
+	return simple_read_from_buffer(ubuf, count, offp, buf, size);
+}
+
+/*
+ * eeprom_attribute - EEPROM sysfs-node attributes
+ *
+ * NOTE Size will be changed in compliance with OF node. EEPROM attribute will
+ * be read-only as well if the corresponding flag is specified in OF node.
+ */
+static BIN_ATTR_RW(eeprom, EEPROM_DEF_SIZE);
+
+/*
+ * csr_dbgfs_ops - CSR debugfs-node read/write operations
+ */
+static const struct file_operations csr_dbgfs_ops = {
+	.owner = THIS_MODULE,
+	.open = simple_open,
+	.write = idt_dbgfs_csr_write,
+	.read = idt_dbgfs_csr_read
+};
+
+/*===========================================================================
+ *                       Driver init/deinit methods
+ *===========================================================================
+ */
+
+/*
+ * idt_set_defval() - disable EEPROM access by default
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_set_defval(struct idt_89hpesx_dev *pdev)
+{
+	/* If OF info is missing then use next values */
+	pdev->eesize = 0;
+	pdev->eero = true;
+	pdev->inieecmd = 0;
+	pdev->eeaddr = 0;
+}
+
+#ifdef CONFIG_OF
+static const struct i2c_device_id ee_ids[];
+/*
+ * idt_ee_match_id() - check whether the node belongs to compatible EEPROMs
+ */
+static const struct i2c_device_id *idt_ee_match_id(struct device_node *node)
+{
+	const struct i2c_device_id *id = ee_ids;
+	char devname[I2C_NAME_SIZE];
+
+	/* Retrieve the device name without manufacturer name */
+	if (of_modalias_node(node, devname, sizeof(devname)))
+		return NULL;
+
+	/* Search through the device name */
+        while (id->name[0]) {
+                if (strcmp(devname, id->name) == 0)
+                        return id;
+                id++;
+        }
+        return NULL;
+}
+
+/*
+ * idt_get_ofdata() - get IDT i2c-device parameters from device tree
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	const struct device_node *node = pdev->client->dev.of_node;
+	struct device *dev = &pdev->client->dev;
+
+	/* Read dts node parameters */
+	if (node) {
+		const struct i2c_device_id *ee_id = NULL;
+		struct device_node *child;
+		const __be32 *addr_be;
+		int len;
+
+		/* Walk through all child nodes looking for compatible one */
+		for_each_available_child_of_node(node, child) {
+			ee_id = idt_ee_match_id(child);
+			if (IS_ERR_OR_NULL(ee_id)) {
+				dev_warn(dev, "Skip unsupported child node %s",
+					child->full_name);
+				continue;
+			} else
+				break;
+		}
+
+		/* If there is no child EEPROM device, then set zero size */
+		if (!ee_id) {
+			idt_set_defval(pdev);
+			return;
+		}
+
+		/* Retrieve EEPROM size */
+		pdev->eesize = (u32)ee_id->driver_data;
+
+		/* Get custom EEPROM address from 'reg' attribute */
+		addr_be = of_get_property(child, "reg", &len);
+		if (!addr_be || (len < sizeof(*addr_be))) {
+			dev_warn(dev, "No reg on %s, use default address %d",
+				child->full_name, EEPROM_DEF_ADDR);
+			pdev->inieecmd = 0;
+			pdev->eeaddr = EEPROM_DEF_ADDR << 1;
+		} else {
+			pdev->inieecmd = EEPROM_USA;
+			pdev->eeaddr = be32_to_cpup(addr_be) << 1;
+		}
+
+		/* Check EEPROM 'read-only' flag */
+		if (of_get_property(child, "read-only", NULL))
+			pdev->eero = true;
+		else /* if (!of_get_property(node, "read-only", NULL)) */
+			pdev->eero = false;
+
+		dev_dbg(dev, "EEPROM of %u bytes found by %hhu",
+			pdev->eesize, pdev->eeaddr);
+	} else {
+		dev_warn(dev, "No dts node, EEPROM access disabled");
+		idt_set_defval(pdev);
+	}
+}
+#else
+static void idt_get_ofdata(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	dev_warn(dev, "OF table is unsupported, EEPROM access disabled");
+
+	/* Nothing we can do, just set the default values */
+	idt_set_defval(pdev);
+}
+#endif /* CONFIG_OF */
+
+/*
+ * idt_create_pdev() - create and init data structure of the driver
+ * @client:	i2c client of IDT PCIe-switch device
+ */
+static struct idt_89hpesx_dev *idt_create_pdev(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev;
+
+	/* Allocate memory for driver data */
+	pdev = devm_kmalloc(&client->dev, sizeof(struct idt_89hpesx_dev),
+		GFP_KERNEL);
+	if (pdev == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	/* Initialize basic fields of the data */
+	pdev->client = client;
+	i2c_set_clientdata(client, pdev);
+
+	/* Read OF nodes information */
+	idt_get_ofdata(pdev);
+
+	/* Initialize basic CSR CMD field - use full DWORD-sized r/w ops */
+	pdev->inicsrcmd = CSR_DWE;
+	pdev->csr = CSR_DEF;
+
+	/* Enable Packet Error Checking if it's supported by adapter */
+	if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) {
+		pdev->iniccode = CCODE_PEC;
+		client->flags |= I2C_CLIENT_PEC;
+	} else /* PEC is unsupported */ {
+		pdev->iniccode = 0;
+	}
+
+	return pdev;
+}
+
+/*
+ * idt_free_pdev() - free data structure of the driver
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_free_pdev(struct idt_89hpesx_dev *pdev)
+{
+	/* Clear driver data from device private field */
+	i2c_set_clientdata(pdev->client, NULL);
+
+	/* Just free memory allocated for data */
+	devm_kfree(&pdev->client->dev, pdev);
+}
+
+/*
+ * idt_set_smbus_ops() - set supported SMBus operations
+ * @pdev:	Pointer to the driver data
+ * Return status of smbus check operations
+ */
+static int idt_set_smbus_ops(struct idt_89hpesx_dev *pdev)
+{
+	struct i2c_adapter *adapter = pdev->client->adapter;
+	struct device *dev = &pdev->client->dev;
+
+	/* Check i2c adapter read functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_READ_BLOCK_DATA)) {
+		pdev->smb_read = idt_smb_read_block;
+		dev_dbg(dev, "SMBus block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+		pdev->smb_read = idt_smb_read_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-read op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_word;
+		dev_warn(dev, "Use slow word/byte SMBus read ops");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
+		pdev->smb_read = idt_smb_read_byte;
+		dev_warn(dev, "Use slow byte SMBus read op");
+	} else /* no supported smbus read operations */ {
+		dev_err(dev, "No supported SMBus read op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Check i2c adapter write functionality */
+	if (i2c_check_functionality(adapter,
+				    I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) {
+		pdev->smb_write = idt_smb_write_block;
+		dev_dbg(dev, "SMBus block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
+		pdev->smb_write = idt_smb_write_i2c_block;
+		dev_dbg(dev, "SMBus i2c-block-write op chosen");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_WORD_DATA) &&
+		   i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_word;
+		dev_warn(dev, "Use slow word/byte SMBus write op");
+	} else if (i2c_check_functionality(adapter,
+					   I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		pdev->smb_write = idt_smb_write_byte;
+		dev_warn(dev, "Use slow byte SMBus write op");
+	} else /* no supported smbus write operations */ {
+		dev_err(dev, "No supported SMBus write op");
+		return -EPFNOSUPPORT;
+	}
+
+	/* Initialize IDT SMBus slave interface mutex */
+	mutex_init(&pdev->smb_mtx);
+
+	return 0;
+}
+
+/*
+ * idt_check_dev() - check whether it's really IDT 89HPESx device
+ * @pdev:	Pointer to the driver data
+ * Return status of i2c adapter check operation
+ */
+static int idt_check_dev(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	u32 viddid;
+	int ret;
+
+	/* Read VID and DID directly from IDT memory space */
+	ret = idt_csr_read(pdev, IDT_VIDDID_CSR, &viddid);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read VID/DID");
+		return ret;
+	}
+
+	/* Check whether it's IDT device */
+	if ((viddid & IDT_VID_MASK) != PCI_VENDOR_ID_IDT) {
+		dev_err(dev, "Got unsupported VID/DID: 0x%08x", viddid);
+		return -ENODEV;
+	}
+
+	dev_info(dev, "Found IDT 89HPES device VID:0x%04x, DID:0x%04x",
+		(viddid & IDT_VID_MASK), (viddid >> 16));
+
+	return 0;
+}
+
+/*
+ * idt_create_sysfs_files() - create sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ * Return status of operation
+ */
+static int idt_create_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+	int ret;
+
+	/* Don't do anything if EEPROM isn't accessible */
+	if (pdev->eesize == 0) {
+		dev_dbg(dev, "Skip creating sysfs-files");
+		return 0;
+	}
+
+	/* Allocate memory for attribute file */
+	pdev->ee_file = devm_kmalloc(dev, sizeof(*pdev->ee_file), GFP_KERNEL);
+	if (!pdev->ee_file)
+		return -ENOMEM;
+
+	/* Copy the declared EEPROM attr structure to change some of fields */
+	memcpy(pdev->ee_file, &bin_attr_eeprom, sizeof(*pdev->ee_file));
+
+	/* In case of read-only EEPROM get rid of write ability */
+	if (pdev->eero) {
+		pdev->ee_file->attr.mode &= ~0200;
+		pdev->ee_file->write = NULL;
+	}
+	/* Create EEPROM sysfs file */
+	pdev->ee_file->size = pdev->eesize;
+	ret = sysfs_create_bin_file(&dev->kobj, pdev->ee_file);
+	if (ret != 0) {
+		kfree(pdev->ee_file);
+		dev_err(dev, "Failed to create EEPROM sysfs-node");
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * idt_remove_sysfs_files() - remove sysfs attribute files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_sysfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct device *dev = &pdev->client->dev;
+
+	/* Don't do anything if EEPROM wasn't accessible */
+	if (pdev->eesize == 0)
+		return;
+
+	/* Remove EEPROM sysfs file */
+	sysfs_remove_bin_file(&dev->kobj, pdev->ee_file);
+
+	/* Free memory allocated for bin_attribute structure */
+	kfree(pdev->ee_file);
+}
+
+/*
+ * idt_create_dbgfs_files() - create debugfs files
+ * @pdev:	Pointer to the driver data
+ */
+#define CSRNAME_LEN	((size_t)32)
+static void idt_create_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	struct i2c_client *cli = pdev->client;
+	char fname[CSRNAME_LEN];
+
+	/* Create Debugfs directory for CSR file */
+	snprintf(fname, CSRNAME_LEN, "%d-%04hx", cli->adapter->nr, cli->addr);
+	pdev->csr_dir = debugfs_create_dir(fname, csr_dbgdir);
+
+	/* Create Debugfs file for CSR read/write operations */
+	pdev->csr_file = debugfs_create_file(cli->name, 0600,
+		pdev->csr_dir, pdev, &csr_dbgfs_ops);
+}
+
+/*
+ * idt_remove_dbgfs_files() - remove debugfs files
+ * @pdev:	Pointer to the driver data
+ */
+static void idt_remove_dbgfs_files(struct idt_89hpesx_dev *pdev)
+{
+	/* Remove CSR directory and it sysfs-node */
+	debugfs_remove_recursive(pdev->csr_dir);
+}
+
+/*
+ * idt_probe() - IDT 89HPESx driver probe() callback method
+ */
+static int idt_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct idt_89hpesx_dev *pdev;
+	int ret;
+
+	/* Create driver data */
+	pdev = idt_create_pdev(client);
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	/* Set SMBus operations */
+	ret = idt_set_smbus_ops(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Check whether it is truly IDT 89HPESx device */
+	ret = idt_check_dev(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create sysfs files */
+	ret = idt_create_sysfs_files(pdev);
+	if (ret != 0)
+		goto err_free_pdev;
+
+	/* Create debugfs files */
+	idt_create_dbgfs_files(pdev);
+
+	return 0;
+
+err_free_pdev:
+	idt_free_pdev(pdev);
+
+	return ret;
+}
+
+/*
+ * idt_remove() - IDT 89HPESx driver remove() callback method
+ */
+static int idt_remove(struct i2c_client *client)
+{
+	struct idt_89hpesx_dev *pdev = i2c_get_clientdata(client);
+
+	/* Remove debugfs files first */
+	idt_remove_dbgfs_files(pdev);
+
+	/* Remove sysfs files */
+	idt_remove_sysfs_files(pdev);
+
+	/* Discard driver data structure */
+	idt_free_pdev(pdev);
+
+	return 0;
+}
+
+/*
+ * ee_ids - array of supported EEPROMs
+ */
+static const struct i2c_device_id ee_ids[] = {
+	{ "24c32",  4096},
+	{ "24c64",  8192},
+	{ "24c128", 16384},
+	{ "24c256", 32768},
+	{ "24c512", 65536},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, ee_ids);
+
+/*
+ * idt_ids - supported IDT 89HPESx devices
+ */
+static const struct i2c_device_id idt_ids[] = {
+	{ "89hpes8nt2", 0 },
+	{ "89hpes12nt3", 0 },
+
+	{ "89hpes24nt6ag2", 0 },
+	{ "89hpes32nt8ag2", 0 },
+	{ "89hpes32nt8bg2", 0 },
+	{ "89hpes12nt12g2", 0 },
+	{ "89hpes16nt16g2", 0 },
+	{ "89hpes24nt24g2", 0 },
+	{ "89hpes32nt24ag2", 0 },
+	{ "89hpes32nt24bg2", 0 },
+
+	{ "89hpes12n3", 0 },
+	{ "89hpes12n3a", 0 },
+	{ "89hpes24n3", 0 },
+	{ "89hpes24n3a", 0 },
+
+	{ "89hpes32h8", 0 },
+	{ "89hpes32h8g2", 0 },
+	{ "89hpes48h12", 0 },
+	{ "89hpes48h12g2", 0 },
+	{ "89hpes48h12ag2", 0 },
+	{ "89hpes16h16", 0 },
+	{ "89hpes22h16", 0 },
+	{ "89hpes22h16g2", 0 },
+	{ "89hpes34h16", 0 },
+	{ "89hpes34h16g2", 0 },
+	{ "89hpes64h16", 0 },
+	{ "89hpes64h16g2", 0 },
+	{ "89hpes64h16ag2", 0 },
+
+	/* { "89hpes3t3", 0 }, // No SMBus-slave iface */
+	{ "89hpes12t3g2", 0 },
+	{ "89hpes24t3g2", 0 },
+	/* { "89hpes4t4", 0 }, // No SMBus-slave iface */
+	{ "89hpes16t4", 0 },
+	{ "89hpes4t4g2", 0 },
+	{ "89hpes10t4g2", 0 },
+	{ "89hpes16t4g2", 0 },
+	{ "89hpes16t4ag2", 0 },
+	{ "89hpes5t5", 0 },
+	{ "89hpes6t5", 0 },
+	{ "89hpes8t5", 0 },
+	{ "89hpes8t5a", 0 },
+	{ "89hpes24t6", 0 },
+	{ "89hpes6t6g2", 0 },
+	{ "89hpes24t6g2", 0 },
+	{ "89hpes16t7", 0 },
+	{ "89hpes32t8", 0 },
+	{ "89hpes32t8g2", 0 },
+	{ "89hpes48t12", 0 },
+	{ "89hpes48t12g2", 0 },
+	{ /* END OF LIST */ }
+};
+MODULE_DEVICE_TABLE(i2c, idt_ids);
+
+/*
+ * idt_driver - IDT 89HPESx driver structure
+ */
+static struct i2c_driver idt_driver = {
+	.driver = {
+		.name = IDT_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = idt_probe,
+	.remove = idt_remove,
+	.id_table = idt_ids,
+};
+
+/*
+ * idt_init() - IDT 89HPESx driver init() callback method
+ */
+static int __init idt_init(void)
+{
+	/* Create Debugfs directory first */
+	if (debugfs_initialized())
+		csr_dbgdir = debugfs_create_dir("idt_csr", NULL);
+
+	/* Add new i2c-device driver */
+	return i2c_add_driver(&idt_driver);
+}
+module_init(idt_init);
+
+/*
+ * idt_exit() - IDT 89HPESx driver exit() callback method
+ */
+static void __exit idt_exit(void)
+{
+	/* Discard debugfs directory and all files if any */
+	debugfs_remove_recursive(csr_dbgdir);
+
+	/* Unregister i2c-device driver */
+	i2c_del_driver(&idt_driver);
+}
+module_exit(idt_exit);
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v5 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2017-01-13 12:16       ` [PATCH v5 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
  2017-01-13 12:16         ` [PATCH v5 1/2] " Serge Semin
@ 2017-01-13 12:16         ` Serge Semin
  2017-01-18 22:34           ` Rob Herring
  1 sibling, 1 reply; 34+ messages in thread
From: Serge Semin @ 2017-01-13 12:16 UTC (permalink / raw)
  To: gregkh, srinivas.kandagatla, andrew, robh+dt, mark.rutland
  Cc: Sergey.Semin, linux-kernel, devicetree, Serge Semin

IDT 89HPESx PCIe-switches exposes SMBus interface to have an access to
the device CSRs and EEPROM. So to properly utilize the interface
functionality, developer should declare a valid dts-file node, which
would refer to the corresponding 89HPESx device.

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
 .../devicetree/bindings/misc/idt_89hpesx.txt       | 44 ++++++++++++++++++++++
 1 file changed, 44 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt

diff --git a/Documentation/devicetree/bindings/misc/idt_89hpesx.txt b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
new file mode 100644
index 0000000..b9093b7
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/idt_89hpesx.txt
@@ -0,0 +1,44 @@
+EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
+
+Required properties:
+  - compatible : should be "<manufacturer>,<type>"
+		 Basically there is only one manufacturer: idt, but some
+		 compatible devices may be produced in future. Following devices
+		 are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
+		 89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
+		 89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
+		 89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
+		 89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
+		 89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
+		 89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
+		 89hpes64h16ag2;
+		 89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
+		 89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
+		 89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
+		 89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
+		 89hpes48t12, 89hpes48t12g2.
+  - reg :	 I2C address of the IDT 89HPESx device.
+
+Optionally there can be EEPROM-compatible subnode:
+  - compatible:  There are five EEPROM devices supported: 24c32, 24c64, 24c128,
+		 24c256 and 24c512 differed by size.
+  - reg:         Custom address of EEPROM device (If not specified IDT 89HPESx
+    (optional)	 device will try to communicate with EEPROM sited by default
+		 address - 0x50)
+  - read-only :	 Parameterless property disables writes to the EEPROM
+    (optional)
+
+Example:
+	idt@60 {
+		compatible = "idt,89hpes32nt8ag2";
+		reg = <0x74>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		eeprom@50 {
+			compatible = "onsemi,24c64";
+			reg = <0x50>;
+			read-only;
+		};
+	};
+
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH v5 2/2] eeprom: Add IDT 89HPESx driver bindings file
  2017-01-13 12:16         ` [PATCH v5 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
@ 2017-01-18 22:34           ` Rob Herring
  0 siblings, 0 replies; 34+ messages in thread
From: Rob Herring @ 2017-01-18 22:34 UTC (permalink / raw)
  To: Serge Semin
  Cc: gregkh, srinivas.kandagatla, andrew, mark.rutland, Sergey.Semin,
	linux-kernel, devicetree

On Fri, Jan 13, 2017 at 03:16:53PM +0300, Serge Semin wrote:
> IDT 89HPESx PCIe-switches exposes SMBus interface to have an access to
> the device CSRs and EEPROM. So to properly utilize the interface
> functionality, developer should declare a valid dts-file node, which
> would refer to the corresponding 89HPESx device.
> 
> Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
> ---
>  .../devicetree/bindings/misc/idt_89hpesx.txt       | 44 ++++++++++++++++++++++
>  1 file changed, 44 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/misc/idt_89hpesx.txt

Acked-by: Rob Herring <robh@kernel.org>

^ permalink raw reply	[flat|nested] 34+ messages in thread

end of thread, other threads:[~2017-01-18 22:34 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-10-02 23:13 [PATCH] Add IDT 89HPESx EEPROM/CSR driver Serge Semin
2016-10-30 13:53 ` Greg KH
2016-11-24 18:42   ` Serge Semin
2016-11-28 22:38 ` [PATCH v2 0/2] eeprom: " Serge Semin
2016-11-28 22:38   ` [PATCH v2 1/2] " Serge Semin
2016-11-29 19:34     ` Greg KH
2016-11-29 19:37     ` Greg KH
2016-11-29 21:16       ` Serge Semin
2016-11-29 21:24         ` Greg KH
2016-11-29 21:45           ` Serge Semin
2016-11-28 22:38   ` [PATCH v2 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
2016-11-29 19:34     ` Greg KH
2016-11-29 21:15       ` Serge Semin
2016-12-05 14:46     ` Rob Herring
2016-12-05 15:25       ` Serge Semin
2016-12-05 17:27         ` Rob Herring
2016-12-05 19:04           ` Serge Semin
2016-12-09  1:57             ` Serge Semin
2016-12-12 23:04             ` Rob Herring
2016-12-13 14:08               ` Serge Semin
2016-11-29 22:27   ` [PATCH v3 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
2016-11-29 22:27     ` [PATCH v3 1/2] " Serge Semin
2016-11-29 22:27     ` [PATCH v3 2/2] eeprom: Add IDT 89HPESx driver dts-binding file Serge Semin
2016-12-13 14:22     ` [PATCH v4 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
2016-12-13 14:22       ` [PATCH v4 1/2] " Serge Semin
2017-01-11  8:21         ` Greg KH
2017-01-12 22:54           ` Serge Semin
2017-01-13  7:22             ` Greg KH
2017-01-13  9:47               ` Serge Semin
2016-12-13 14:22       ` [PATCH v4 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
2017-01-13 12:16       ` [PATCH v5 0/2] eeprom: Add IDT 89HPESx EEPROM/CSR driver Serge Semin
2017-01-13 12:16         ` [PATCH v5 1/2] " Serge Semin
2017-01-13 12:16         ` [PATCH v5 2/2] eeprom: Add IDT 89HPESx driver bindings file Serge Semin
2017-01-18 22:34           ` Rob Herring

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).