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@xxxxxxxxx> 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@xxxxxxxxx>, <Sergey.Semin@xxxxxxxxxxxxxx> > + */ > +/* > + * 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 -- To unsubscribe from this list: send the line "unsubscribe linux-i2c" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html