// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2016 T-Platforms. All Rights Reserved. * * IDT PCIe-switch NTB Linux driver * * Contact Information: * Serge Semin <[email protected]>, <[email protected]> */ /* * 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/property.h> #include <linux/i2c.h> #include <linux/pci_ids.h> #include <linux/delay.h> #define IDT_NAME … #define IDT_89HPESX_DESC … #define IDT_89HPESX_VER … MODULE_DESCRIPTION(…); MODULE_VERSION(…); MODULE_LICENSE(…) …; MODULE_AUTHOR(…) …; /* * 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 */ struct idt_smb_seq; struct idt_89hpesx_dev { … }; /* * 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 { … }; /* * 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 { … } __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 { … } __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 … #define CCODE_START … #define CCODE_CSR … #define CCODE_EEPROM … #define CCODE_BYTE … #define CCODE_WORD … #define CCODE_BLOCK … #define CCODE_PEC … /* * 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 … #define EEPROM_OP_READ … #define EEPROM_USA … #define EEPROM_NAERR … #define EEPROM_LAERR … #define EEPROM_MSS … #define EEPROM_WR_CNT … #define EEPROM_WRRD_CNT … #define EEPROM_RD_CNT … #define EEPROM_DEF_SIZE … #define EEPROM_DEF_ADDR … #define EEPROM_TOUT … /* * 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 … #define CSR_OP_WRITE … #define CSR_OP_READ … #define CSR_RERR … #define CSR_WERR … #define CSR_WR_CNT … #define CSR_WRRD_CNT … #define CSR_RD_CNT … #define CSR_MAX … #define CSR_DEF … #define CSR_REAL_ADDR(val) … /* * IDT 89HPESx basic register * @IDT_VIDDID_CSR: PCIe VID and DID of IDT 89HPESx * @IDT_VID_MASK: Mask of VID */ #define IDT_VIDDID_CSR … #define IDT_VID_MASK … /* * 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 … #define idt_smb_safe(ops, 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) { … } /* * 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) { … } /* * 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) { … } /* * 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) { … } /* * 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) { … } /* * 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) { … } /* * 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) { … } /* * 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) { … } /*=========================================================================== * 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) { … } /* * 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) { … } /* * 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) { … } /*=========================================================================== * 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) { … } /* * 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) { … } /*=========================================================================== * 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) { … } /* * 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) { … } /* * 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) { … } /* * 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 … static ssize_t idt_dbgfs_csr_read(struct file *filep, char __user *ubuf, size_t count, loff_t *offp) { … } /* * 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 = …; /*=========================================================================== * 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) { … } 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 fwnode_handle *fwnode) { … } /* * idt_get_fw_data() - get IDT i2c-device parameters from device tree * @pdev: Pointer to the driver data */ static void idt_get_fw_data(struct idt_89hpesx_dev *pdev) { … } /* * 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) { … } /* * 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) { … } /* * 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) { … } /* * 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) { … } /* * 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) { … } /* * 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) { … } /* * idt_create_dbgfs_files() - create debugfs files * @pdev: Pointer to the driver data */ #define CSRNAME_LEN … static void idt_create_dbgfs_files(struct idt_89hpesx_dev *pdev) { … } /* * idt_remove_dbgfs_files() - remove debugfs files * @pdev: Pointer to the driver data */ static void idt_remove_dbgfs_files(struct idt_89hpesx_dev *pdev) { … } /* * idt_probe() - IDT 89HPESx driver probe() callback method */ static int idt_probe(struct i2c_client *client) { … } /* * idt_remove() - IDT 89HPESx driver remove() callback method */ static void idt_remove(struct i2c_client *client) { … } /* * ee_ids - array of supported EEPROMs */ static const struct i2c_device_id ee_ids[] = …; MODULE_DEVICE_TABLE(i2c, ee_ids); /* * idt_ids - supported IDT 89HPESx devices */ static const struct i2c_device_id idt_ids[] = …; MODULE_DEVICE_TABLE(i2c, idt_ids); static const struct of_device_id idt_of_match[] = …; MODULE_DEVICE_TABLE(of, idt_of_match); /* * idt_driver - IDT 89HPESx driver structure */ static struct i2c_driver idt_driver = …; /* * idt_init() - IDT 89HPESx driver init() callback method */ static int __init idt_init(void) { … } module_init(…) …; /* * idt_exit() - IDT 89HPESx driver exit() callback method */ static void __exit idt_exit(void) { … } module_exit(idt_exit);