| /* |
| <:copyright-BRCM:2016:DUAL/GPL:standard |
| |
| Broadcom Proprietary and Confidential.(c) 2016 Broadcom |
| All Rights Reserved |
| |
| Unless you and Broadcom execute a separate written software license |
| agreement governing use of this software, this software is licensed |
| to you under the terms of the GNU General Public License version 2 |
| (the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php, |
| with the following added to such license: |
| |
| As a special exception, the copyright holders of this software give |
| you permission to link this software with independent modules, and |
| to copy and distribute the resulting executable under terms of your |
| choice, provided that you also meet, for each linked independent |
| module, the terms and conditions of the license of that module. |
| An independent module is a module which is not derived from this |
| software. The special exception does not apply to any modifications |
| of the software. |
| |
| Not withstanding the above, under no circumstances may you combine |
| this software in any way with any other Broadcom software provided |
| under a license other than the GPL, without Broadcom's express prior |
| written consent. |
| |
| :> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/jiffies.h> |
| #include <linux/i2c.h> |
| #include <linux/err.h> |
| #include <linux/cdev.h> |
| #include <asm/uaccess.h> /*copy_from_user*/ |
| #include <linux/proc_fs.h> |
| #include "bcmos_system.h" |
| #include "bcmolt_i2c_devs.h" |
| #include "bcmolt_i2c_devs_ioctl.h" |
| |
| #define BCM_I2C_DEV_ADDR_START typedef enum { |
| #define BCM_I2C_DEV_ADDR(name, desc, val) name = val, |
| #define BCM_I2C_DEV_ADDR_END } bcm_i2c_dev_addr; |
| |
| #include "bcmolt_i2c_devs_addr.h" |
| |
| #define PROC_DIR_NAME "maple_i2c" |
| #define PROC_ENTRY_NAME "i2c" |
| #define MAX_EAGAIN_ITERS 200 /* 10ms per iter */ |
| |
| /* uncomment the next line to enable debug output to the kernel CLI */ |
| /* #define I2C_DEBUG_PRINTS 1 */ |
| #ifdef I2C_DEBUG_PRINTS |
| #define i2c_debug_print(fmt, args...) printk("%s#%d: " fmt, __FUNCTION__, __LINE__, ## args) |
| #else |
| #define i2c_debug_print(...) |
| #endif |
| |
| int maple_i2c_chrdev_major = 216; |
| module_param(maple_i2c_chrdev_major, int, S_IRUSR | S_IRGRP | S_IWGRP); |
| MODULE_PARM_DESC(maple_i2c_chrdev_major, "maple_i2c_major"); |
| |
| static struct proc_dir_entry *i2c_proc_dir; |
| static struct proc_dir_entry *i2c_proc_entry; |
| static char is_chrdev_reg; |
| static char is_cdev_add; |
| static int i2c_client_num; |
| static int i2c_num_clients; |
| |
| const unsigned short normal_i2c[] = { |
| I2C_SW0_I2C_ADDR, |
| I2C_SW1_I2C_ADDR, |
| I2C_SW2_I2C_ADDR, |
| I2C_SW3_I2C_ADDR, |
| I2C_SW4_I2C_ADDR, |
| SFP_I2C_ADDR1, |
| SFP_I2C_ADDR2, |
| FPGA_I2C_ADDR, |
| PON_DPLL_I2C_ADDR, |
| PM_DPLL_I2C_ADDR, |
| CXP_R_I2C_ADDR, |
| PCIE_SW_I2C_ADDR, |
| I2C_CLIENT_END |
| }; |
| |
| static unsigned char slave_valid[SLAVE_NUM_OF_SLAVES] = {0}; |
| |
| /* |
| * Driver data (common to all clients) |
| */ |
| |
| static const struct i2c_device_id maple_i2c_id_table[] = { |
| { "maple_i2c", 0 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, maple_i2c_id_table); |
| |
| static void bcm_access_fpga(void); |
| |
| /* |
| * Client data (each client gets its own) |
| */ |
| struct maple_i2c_data { |
| struct i2c_client client; |
| }; |
| |
| static struct maple_i2c_data *pclient_data_ar[SLAVE_NUM_OF_SLAVES]; |
| |
| static int get_client(unsigned char client_num, struct i2c_client **client) |
| { |
| if ((client_num >= SLAVE_NUM_OF_SLAVES) || !slave_valid[client_num]) |
| { |
| i2c_debug_print("I2C error: Wrong client number 0x%x\n", client_num); |
| return -1; |
| } |
| *client = &pclient_data_ar[client_num]->client; |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Helper functions which attempt I2C transfer until successful, or error code is other than -EAGAIN |
| */ |
| |
| static int maple_i2c_send(unsigned char client_num, u8 *buf, size_t count) |
| { |
| struct i2c_client *client; |
| int rc; |
| int niter = 0; |
| |
| if (get_client(client_num, &client)) |
| return -1; |
| |
| do { |
| rc = i2c_master_send(client, buf, count); |
| if (rc == -EAGAIN) |
| msleep(10); |
| } while (rc == -EAGAIN && ++niter < MAX_EAGAIN_ITERS); |
| rc = (rc == count) ? 0 : (rc < 0 ) ? rc : -1; |
| |
| return rc; |
| } |
| |
| static int maple_i2c_recv(unsigned char client_num, u8 *buf, size_t count) |
| { |
| struct i2c_client *client; |
| int rc; |
| int niter = 0; |
| |
| if (get_client(client_num, &client)) |
| return -1; |
| |
| do { |
| rc = i2c_master_recv(client, buf, count); |
| if (rc == -EAGAIN) |
| msleep(10); |
| } while (rc == -EAGAIN && ++niter < MAX_EAGAIN_ITERS); |
| rc = (rc == count) ? 0 : (rc < 0 ) ? rc : -1; |
| |
| return rc; |
| } |
| |
| |
| static int maple_i2c_read_with_offset(unsigned char client_num, u8 *offset, size_t offset_size, u8 *buf, size_t count) |
| { |
| struct i2c_client *client; |
| struct i2c_msg msg[2]; |
| int rc; |
| int niter = 0; |
| |
| if (get_client(client_num, &client)) |
| return -1; |
| |
| msg[0].addr = msg[1].addr = client->addr; |
| msg[0].flags = msg[1].flags = client->flags & I2C_M_TEN; |
| |
| msg[0].len = offset_size; |
| msg[0].buf = offset; |
| |
| msg[1].flags |= I2C_M_RD; |
| msg[1].len = count; |
| msg[1].buf = buf; |
| |
| do { |
| rc = i2c_transfer(client->adapter, msg, 2); |
| if (rc == -EAGAIN) |
| msleep(10); |
| } while (rc == -EAGAIN && ++niter < MAX_EAGAIN_ITERS); |
| |
| rc = (rc == 2) ? 0 : (rc < 0 ) ? rc : -1; |
| |
| return rc; |
| } |
| |
| ssize_t maple_i2c_write(unsigned char client_num, char *buf, size_t count) |
| { |
| int rc; |
| |
| i2c_debug_print("Write to switch: client=0x%x val = 0x%x\n", normal_i2c[client_num], *buf); |
| rc = maple_i2c_send(client_num, (u8 *)buf, count); |
| |
| if (rc < 0) |
| i2c_debug_print("Write Failed. rc=%d client=%u[%u]\n", rc, |
| normal_i2c[client_num], client_num); |
| else |
| i2c_debug_print("Write Successful client=%u[%u]\n", |
| normal_i2c[client_num], client_num); |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_write); |
| |
| ssize_t maple_i2c_read(unsigned char client_num, char *buf, size_t count) |
| { |
| int rc; |
| |
| i2c_debug_print("Read from switch: client 0x%x\n", normal_i2c[client_num]); |
| rc = maple_i2c_recv(client_num, (u8 *)buf, count); |
| |
| if (rc < 0) |
| i2c_debug_print("Read Failed. rc=%d client=%u[%u]\n", rc, |
| normal_i2c[client_num], client_num); |
| else |
| i2c_debug_print("Read Successful: count=%u val = 0x%x\n", (unsigned)count, *buf); |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_read); |
| |
| int maple_i2c_write_byte(unsigned char client_num, unsigned char offset, unsigned char val) |
| { |
| u8 buf[2]; |
| int rc; |
| |
| i2c_debug_print("Write Byte: client=0x%x offset = 0x%x, val = 0x%x\n", normal_i2c[client_num], offset, val); |
| |
| buf[0] = offset; |
| buf[1] = val; |
| rc = maple_i2c_send(client_num, buf, 2); |
| |
| if (rc < 0) |
| i2c_debug_print("Write Byte Failed. rc=%d offset=0x%x val=0x%x client=%u[%u]\n", |
| rc, offset, val, |
| normal_i2c[client_num], client_num); |
| else |
| i2c_debug_print("Write Byte: offset=0x%x val=0x%x client=%u/%u\n", |
| offset, val, |
| normal_i2c[client_num], client_num); |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_write_byte); |
| |
| int maple_i2c_read_byte(unsigned char client_num, unsigned char offset, unsigned char *data) |
| { |
| u8 o = offset; |
| u8 val; |
| int rc; |
| |
| i2c_debug_print("Write Byte: client=0x%x offset = 0x%x, val = 0x%x\n", normal_i2c[client_num], offset, val); |
| |
| rc = maple_i2c_read_with_offset(client_num, &o, 1, &val, 1); |
| |
| if (rc < 0) |
| i2c_debug_print("Read Byte Failed. rc=%d client=%u[%u]\n", rc, |
| normal_i2c[client_num], client_num); |
| else |
| { |
| *data = val; |
| i2c_debug_print("Read Byte: client=0x%x offset = 0x%x, val = 0x%x\n", |
| normal_i2c[client_num], offset, *data); |
| } |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_read_byte); |
| |
| |
| int maple_i2c_write_word(unsigned char client_num, unsigned char offset, unsigned short val) |
| { |
| u8 buf[3]; |
| int rc; |
| |
| i2c_debug_print("Write Word: offset = 0x%x, val = 0x%x\n", offset, val); |
| |
| /* The offset to be written should be the first byte in the I2C write */ |
| buf[0] = offset; |
| buf[1] = (char)(val&0xFF); |
| buf[2] = (char)(val>>8); |
| rc = maple_i2c_send(client_num, buf, sizeof(buf)); |
| |
| if (rc < 0) |
| i2c_debug_print("Write Word Failed. rc=%d offset=0x%x val=0x%x, client=%u[%u]\n", |
| rc, offset, val, |
| normal_i2c[client_num], client_num); |
| else |
| i2c_debug_print("Write Word: offset=0x%x val=0x%x, client=%u[%u]\n", |
| offset, val, |
| normal_i2c[client_num], client_num); |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_write_word); |
| |
| int maple_i2c_read_word(unsigned char client_num, unsigned char offset, unsigned short *data) |
| { |
| u8 o = offset; |
| u8 buf[2]; |
| int rc; |
| |
| rc = maple_i2c_read_with_offset(client_num, &o, 1, buf, sizeof(buf)); |
| |
| if (rc < 0) |
| i2c_debug_print("Read Word Failed. rc=%d offset=0x%x client=%u[%u]\n", |
| rc, offset, |
| normal_i2c[client_num], client_num); |
| else |
| { |
| *data = (buf[1]<<8) | buf[0]; /* I2C data is LE */ |
| i2c_debug_print("Read Word: offset = 0x%x, val = 0x%x client=%u[%u]\n", |
| offset, *data, |
| normal_i2c[client_num], client_num); |
| } |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_read_word); |
| |
| |
| int maple_i2c_write_reg(unsigned char client_num, unsigned char offset, int val) |
| { |
| u8 buf[5]; |
| int rc; |
| |
| i2c_debug_print("Write Register: offset = 0x%x, val = 0x%x\n", offset, val); |
| |
| /* Set the buf[0] to be the offset for write operation */ |
| buf[0] = offset; |
| |
| /* On the I2C bus, LS Byte should go first */ |
| buf[1] = val & 0xff; |
| buf[2] = (val >> 8) & 0xff; |
| buf[3] = (val >> 16) & 0xff; |
| buf[4] = (val >> 24) & 0xff; |
| rc = maple_i2c_send(client_num, buf, sizeof(buf)); |
| |
| if (rc < 0) |
| i2c_debug_print("Write Reg Failed. rc=%d offset=0x%x val=0x%x client=%u[%u]\n", |
| rc, offset, val, |
| normal_i2c[client_num], client_num); |
| else |
| i2c_debug_print("Write Reg Successful. offset=0x%x val=0x%x client=%u[%u]\n", |
| offset, val, |
| normal_i2c[client_num], client_num); |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_write_reg); |
| |
| |
| int maple_i2c_read_reg(unsigned char client_num, unsigned char offset, unsigned int *data) |
| { |
| u8 o = offset; |
| u8 buf[4]; |
| int rc; |
| |
| rc = maple_i2c_read_with_offset(client_num, &o, 1, buf, sizeof(buf)); |
| |
| if (rc < 0) |
| i2c_debug_print("Read Reg Failed. rc=%d offset=0x%x client=%u[%u]\n", |
| rc, offset, |
| normal_i2c[client_num], client_num); |
| else |
| { |
| *data = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); /* LS byte is transferred first */ |
| i2c_debug_print("Read Register: client=0x%x offset = 0x%x, val = 0x%x\n", normal_i2c[client_num], offset, *data); |
| } |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_read_reg); |
| |
| |
| int maple_i2c_write_fpga(unsigned char client_num, unsigned int offset, unsigned int val) |
| { |
| u8 buf[8]; |
| int rc; |
| |
| bcm_access_fpga(); |
| |
| i2c_debug_print("Write fpga: offset = 0x%x, val = 0x%x\n", offset, val); |
| |
| /* Set the offset for write operation */ |
| // offset = swab32(offset); |
| memcpy(&buf[0], (char*)&offset, 4); |
| |
| /* On the I2C bus, LS Byte should go first */ |
| // val = swab32(val); |
| memcpy(&buf[4], (char*)&val, 4); |
| |
| rc = maple_i2c_send(client_num, buf, sizeof(buf)); |
| |
| if (rc < 0) |
| i2c_debug_print("Write FPGA Failed. rc=%d offset=0x%x val=0x%x client=%u[%u]\n", |
| rc, offset, val, |
| normal_i2c[client_num], client_num); |
| else |
| i2c_debug_print("Write FPGA: offset=0x%x val=0x%x client=%u[%u]\n", |
| offset, val, |
| normal_i2c[client_num], client_num); |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_write_fpga); |
| |
| int maple_i2c_read_fpga(unsigned char client_num, unsigned int offset, unsigned int *data) |
| { |
| u8 obuf[4]; |
| u8 buf[4]; |
| int rc; |
| |
| bcm_access_fpga(); |
| |
| /* FPGA expects 4 byte offset in BIG ENDIAN format */ |
| obuf[0] = (offset >> 24) & 0xff; |
| obuf[1] = (offset >> 16) & 0xff; |
| obuf[2] = (offset >> 8) & 0xff; |
| obuf[3] = offset & 0xff; |
| rc = maple_i2c_read_with_offset(client_num, obuf, sizeof(obuf), buf, sizeof(buf)); |
| |
| if (rc < 0) |
| i2c_debug_print("Read FPGA Failed. rc=%d offset=0x%x client=%u[%u]\n", |
| rc, offset, |
| normal_i2c[client_num], client_num); |
| else |
| { |
| *data = buf[3] | (buf[2] << 8) | (buf[1] << 16) | (buf[0] << 24); /* MS byte is transferred first */ |
| i2c_debug_print("Read FPGA: offset = 0x%x, val = 0x%x %02x %02x %02x %02x client=%u[%u]\n", |
| offset, *data, buf[0], buf[1], buf[2], buf[3], |
| normal_i2c[client_num], client_num); |
| } |
| return rc; |
| } |
| EXPORT_SYMBOL(maple_i2c_read_fpga); |
| |
| enum client_num maple_i2c_get_client(u32 addr) |
| { |
| enum client_num i = 0; |
| while (i < SLAVE_NUM_OF_SLAVES) |
| { |
| if (pclient_data_ar[i]) |
| { |
| if (slave_valid[i] && pclient_data_ar[i]->client.addr == addr) |
| { |
| break; |
| } |
| } |
| i++; |
| } |
| return i; |
| } |
| EXPORT_SYMBOL(maple_i2c_get_client); |
| |
| static int dev_change(u32 addr) |
| { |
| enum client_num i = maple_i2c_get_client(addr); |
| |
| if (i < SLAVE_NUM_OF_SLAVES) |
| { |
| i2c_client_num = i; |
| i2c_debug_print("Slave was set successfully i %d, addr 0x%x\n", i, addr); |
| } |
| else |
| { |
| i2c_debug_print("Failed to set slave i %d, addr 0x%x\n", i, addr); |
| } |
| |
| return 0; |
| } |
| |
| static void bcm_access_fpga(void) |
| { |
| static int access_enabled; |
| int fpga_client_num = maple_i2c_get_client(I2C_SW1_I2C_ADDR); |
| uint8_t b; |
| |
| if (access_enabled) |
| return; |
| b = 4; |
| maple_i2c_write(fpga_client_num, &b, 1); |
| access_enabled = 1; |
| } |
| #define BCM_FPGA_REG_RESETS 0x2 |
| #define BCM_FPGA_REG_FPGA_VERSION 0x0 |
| /* 1 = out of reset bcm_board_config_reset_value */ |
| void reset_maple(int bit,int param) |
| { |
| int fpga_client_num = maple_i2c_get_client(FPGA_I2C_ADDR); |
| int sw_client_num = maple_i2c_get_client(I2C_SW0_I2C_ADDR); |
| uint8_t b = 1; |
| uint32_t reset_values; |
| |
| maple_i2c_write(sw_client_num, &b, 1); |
| |
| /* Read-modify-write so we can set only the relevant bit */ |
| maple_i2c_read_fpga(fpga_client_num, BCM_FPGA_REG_RESETS, &reset_values); |
| |
| if (param) |
| reset_values |= 1 << bit; |
| else |
| reset_values &= ~(1 << bit); |
| maple_i2c_write_fpga(fpga_client_num, BCM_FPGA_REG_RESETS, reset_values); |
| } |
| EXPORT_SYMBOL(reset_maple); |
| |
| /* Calls the appropriate function based on user command */ |
| static int exec_command(const char *buf, size_t count) |
| { |
| #define MAX_ARGS 4 |
| #define MAX_ARG_SIZE 32 |
| int i, argc = 0, val = 0; |
| char cmd; |
| u32 offset = 0; |
| char arg[MAX_ARGS][MAX_ARG_SIZE]; |
| #define LOG_WR_KBUF_SIZE 128 |
| char kbuf[LOG_WR_KBUF_SIZE]; |
| |
| if ((count > LOG_WR_KBUF_SIZE-1) || (copy_from_user(kbuf, buf, count) != 0)) |
| return -EFAULT; |
| kbuf[count]=0; |
| argc = sscanf(kbuf, "%c %s %s %s %s", &cmd, arg[0], arg[1], arg[2], arg[3]); |
| |
| if (argc < 1) |
| { |
| printk("Need at-least 2 arguments\n"); |
| return -EFAULT; |
| } |
| |
| for (i=0; i<MAX_ARGS; ++i) { |
| arg[i][MAX_ARG_SIZE-1] = '\0'; |
| } |
| offset = (u32)simple_strtoul(arg[0], NULL, 0); |
| if (argc == 3) |
| val = (int)simple_strtoul(arg[1], NULL, 0); |
| |
| switch (cmd) |
| { |
| case 'm': |
| reset_maple(0,offset); // maple out of reset(1) | reset (0) |
| break; |
| case 'k': |
| reset_maple(4,offset); // katana2 out of reset(1) | reset (0) |
| break; |
| case 'b': |
| if (argc == 3) |
| maple_i2c_write_byte(i2c_client_num, (u8)offset, (u8)val); |
| else |
| { |
| u8 data; |
| maple_i2c_read_byte(i2c_client_num, (u8)offset, &data); |
| printk("client 0x%x, offset 0x%x, data=0x%x\n", normal_i2c[i2c_client_num], offset, data); |
| } |
| break; |
| |
| case 's': |
| if (argc == 2) |
| { |
| u8 data = (u8)offset; |
| maple_i2c_write(i2c_client_num, &data, 1); |
| } |
| else |
| { |
| u8 data = offset; |
| maple_i2c_read(i2c_client_num, &data, 1); |
| printk("client 0x%x, data=0x%x\n", normal_i2c[i2c_client_num], data); |
| } |
| break; |
| |
| case 'w': |
| if (argc == 3) |
| maple_i2c_write_word(i2c_client_num, (u8)offset, (u16)val); |
| else |
| { |
| u16 data; |
| maple_i2c_read_word(i2c_client_num, (u8)offset, &data); |
| printk("client 0x%x, offset 0x%x, data=0x%x\n", normal_i2c[i2c_client_num], offset, data); |
| } |
| break; |
| |
| case 'd': |
| if (argc == 3) |
| maple_i2c_write_reg(i2c_client_num, (u8)offset, val); |
| else |
| { |
| u32 data; |
| maple_i2c_read_reg(i2c_client_num, (u8)offset, &data); |
| printk("client 0x%x, offset 0x%x, data=0x%x\n", normal_i2c[i2c_client_num], offset, data); |
| } |
| break; |
| |
| case 'f': |
| if (argc == 3) |
| maple_i2c_write_fpga(i2c_client_num, offset, val); |
| else |
| { |
| u32 data; |
| maple_i2c_read_fpga(i2c_client_num, offset, &data); |
| printk("client 0x%x, offset 0x%x, data=0x%x\n", normal_i2c[i2c_client_num], offset, data); |
| } |
| break; |
| |
| case 'c': |
| dev_change(offset); |
| break; |
| |
| default: |
| printk("Invalid command.\n Valid commands:\n" |
| " Change I2C Addr: c addr\n" |
| " Write Reg: d offset val\n" |
| " Read Reg: d offset\n" |
| " Write Word: w offset val\n" |
| " Read Word: w offset\n" |
| " Write Byte: b offset val\n" |
| " Read Byte: b offset\n" |
| " Write fpga: f offset val\n" |
| " Read fpga: f offset\n" |
| " Write to switch: s val (one byte)\n" |
| " Read from switch:s\n" |
| " Maple: m <0|1>\n" |
| " Katana2: k <0|1>\n" |
| ); |
| break; |
| } |
| return count; |
| } |
| |
| |
| |
| /* Read Function of PROCFS attribute "maple_i2c/test" */ |
| static ssize_t maple_i2c_proc_test_read(struct file *f, char *buf, size_t count, |
| loff_t *pos) |
| { |
| printk(" Usage: echo command > " |
| " /proc/maple_i2c/test\n"); |
| printk(" supported commands:\n" |
| " Change I2C Addr: c addr\n" |
| " Write Reg: d offset val\n" |
| " Read Reg: d offset\n" |
| " Write Word: w offset val\n" |
| " Read Word: w offset\n" |
| " Write Byte: b offset val\n" |
| " Read Byte: b offset\n" |
| " Write fpga: f offset val\n" |
| " Read fpga: f offset\n" |
| " Write to switch: s val (one byte)\n" |
| " Read from switch:s\n" |
| ); |
| return 0; |
| } |
| |
| /* Write Function of PROCFS attribute "maple_i2c/test" */ |
| static ssize_t maple_i2c_proc_test_write(struct file *f, const char *buf, |
| size_t count, loff_t *pos) |
| { |
| return exec_command(buf, count); |
| } |
| |
| /* Return 0 if detection is successful, -ENODEV otherwise */ |
| static int maple_i2c_dev_detect(struct i2c_client *client, struct i2c_board_info *info) |
| { |
| i2c_debug_print("detecting i2c device 0x%x\n", client->addr); |
| |
| /* TODO: detection and identification */ |
| |
| strcpy(info->type, "maple_i2c"); |
| info->flags = 0; |
| return 0; |
| } |
| |
| static struct file_operations maple_i2c_fops = |
| { |
| read: maple_i2c_proc_test_read, |
| write: maple_i2c_proc_test_write |
| }; |
| |
| static int maple_i2c_dev_create_proc(void) |
| { |
| i2c_proc_dir = proc_mkdir(PROC_DIR_NAME, NULL); |
| if (!i2c_proc_dir) |
| { |
| i2c_debug_print("fail to create proc dir\n"); |
| return -ENOMEM; |
| } |
| |
| i2c_proc_entry = create_proc_entry(PROC_ENTRY_NAME, 0, i2c_proc_dir); |
| if (!i2c_proc_entry) |
| { |
| remove_proc_entry(PROC_DIR_NAME, NULL); |
| return -ENOMEM; |
| } |
| |
| i2c_proc_entry->proc_fops = &maple_i2c_fops; |
| |
| return 0; |
| } |
| |
| static int maple_i2c_chrdev_open(struct inode *inode, struct file *filp) |
| { |
| return 0; |
| } |
| |
| static int maple_i2c_chrdev_release(struct inode *inode, struct file *filp) |
| { |
| return 0; |
| } |
| |
| static long maple_i2c_chrdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| int linux_rc = 0; |
| int rc; |
| maple_i2c_ioctl_param params; |
| |
| /* don't even decode wrong cmds: better returning ENOTTY than EFAULT */ |
| if (_IOC_TYPE(cmd) != MAPLE_I2C_IOCTL_MAGIC) |
| return -ENOTTY; |
| |
| /* |
| * the type is a bitmask, and VERIFY_WRITE catches R/W |
| * transfers. Note that the type is user-oriented, while |
| * verify_area is kernel-oriented, so the concept of "read" and |
| * "write" is reversed |
| */ |
| if (_IOC_DIR(cmd) & _IOC_READ) |
| linux_rc = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); |
| else if (_IOC_DIR(cmd) & _IOC_WRITE) |
| linux_rc = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); |
| if (linux_rc) |
| return -EFAULT; |
| |
| switch (cmd) |
| { |
| case MAPLE_I2C_IOCTL_OP_DEV_CHANGE: |
| { |
| linux_rc = copy_from_user((char *)¶ms, (char *)arg, sizeof(params)); |
| if (linux_rc < 0) |
| break; |
| return dev_change(params.addr); |
| } |
| break; |
| case MAPLE_I2C_IOCTL_OP_DEV_WRITE: |
| { |
| linux_rc = copy_from_user((char *)¶ms, (char *)arg, sizeof(params)); |
| if (linux_rc < 0) |
| break; |
| switch (params.count) |
| { |
| case 8: |
| return maple_i2c_write_byte(i2c_client_num, params.addr, params.val); |
| case 16: |
| return maple_i2c_write_word(i2c_client_num, params.addr, params.val); |
| case 32: |
| return maple_i2c_write_reg(i2c_client_num, params.addr, params.val); |
| default: |
| return -EINVAL; |
| } |
| } |
| break; |
| case MAPLE_I2C_IOCTL_OP_DEV_READ: |
| { |
| unsigned char b; |
| unsigned short w; |
| unsigned int r; |
| |
| linux_rc = copy_from_user((char *)¶ms, (char *)arg, sizeof(params)); |
| if (linux_rc < 0) |
| break; |
| switch (params.count) |
| { |
| case 8: |
| linux_rc = maple_i2c_read_byte(i2c_client_num, params.addr, &b); |
| if (linux_rc < 0) |
| break; |
| params.val = b; |
| break; |
| case 16: |
| linux_rc = maple_i2c_read_word(i2c_client_num, params.addr, &w); |
| if (linux_rc < 0) |
| break; |
| params.val = w; |
| break; |
| case 32: |
| linux_rc = maple_i2c_read_reg(i2c_client_num, params.addr, &r); |
| if (linux_rc < 0) |
| break; |
| params.val = r; |
| break; |
| default: |
| return -EINVAL; |
| } |
| if (linux_rc < 0) |
| break; |
| return copy_to_user((char *)arg, (char *)¶ms, sizeof(params)); |
| } |
| case MAPLE_I2C_IOCTL_OP_SWITCH_WRITE: |
| { |
| char b; |
| |
| linux_rc = copy_from_user((char *)¶ms, (char *)arg, sizeof(params)); |
| if (linux_rc < 0) |
| break; |
| b = params.val; |
| return maple_i2c_write(i2c_client_num, &b, 1); |
| } |
| case MAPLE_I2C_IOCTL_OP_SWITCH_READ: |
| { |
| char b; |
| |
| linux_rc = maple_i2c_read(i2c_client_num, &b, 1); |
| if (linux_rc < 0) |
| break; |
| params.val = b; |
| return copy_to_user((char *)arg, (char *)¶ms, sizeof(params)); |
| } |
| case MAPLE_I2C_IOCTL_OP_FPGA_WRITE: |
| { |
| linux_rc = copy_from_user((char *)¶ms, (char *)arg, sizeof(params)); |
| if (linux_rc < 0) |
| break; |
| return maple_i2c_write_fpga(i2c_client_num, params.addr, params.val); |
| } |
| case MAPLE_I2C_IOCTL_OP_FPGA_READ: |
| { |
| linux_rc = copy_from_user((char *)¶ms, (char *)arg, sizeof(params)); |
| if (linux_rc < 0) |
| break; |
| linux_rc = maple_i2c_read_fpga(i2c_client_num, params.addr, ¶ms.val); |
| if (linux_rc < 0) |
| break; |
| return copy_to_user((char *)arg, (char *)¶ms, sizeof(params)); |
| } |
| default: |
| rc = -ENOTTY; |
| break; |
| } |
| |
| return linux_rc; |
| } |
| |
| static ssize_t maple_i2c_chrdev_write(struct file *filp, const char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| return 0; |
| } |
| |
| static struct file_operations maple_i2c_chrdev_fops = |
| { |
| .owner = THIS_MODULE, |
| .open = maple_i2c_chrdev_open, |
| .release = maple_i2c_chrdev_release, |
| .write = maple_i2c_chrdev_write, |
| .unlocked_ioctl = maple_i2c_chrdev_ioctl |
| }; |
| |
| static struct cdev maple_i2c_chrdev_cdev; |
| |
| static int maple_i2c_dev_create_chrdev(void) |
| { |
| dev_t dev = MKDEV(maple_i2c_chrdev_major, 0); |
| int linux_rc; |
| |
| is_chrdev_reg = 0; |
| is_cdev_add = 0; |
| /* |
| * Register your major, and accept a dynamic number. |
| */ |
| if (!maple_i2c_chrdev_major) |
| return -1; |
| linux_rc = register_chrdev_region(dev, 0, "maple_i2c"); |
| if (linux_rc < 0) |
| { |
| i2c_debug_print("register_chrdev_region()->%d\n", linux_rc); |
| return -EIO; |
| } |
| is_chrdev_reg = 1; |
| |
| cdev_init(&maple_i2c_chrdev_cdev, &maple_i2c_chrdev_fops); |
| linux_rc = cdev_add(&maple_i2c_chrdev_cdev, dev, 1); |
| if (linux_rc < 0) |
| { |
| i2c_debug_print("cdev_add()->%d\n", linux_rc); |
| return -EIO; |
| } |
| is_cdev_add = 1; |
| |
| return 0; |
| } |
| |
| static int maple_i2c_dev_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| int err = 0; |
| struct maple_i2c_data *pclient_data; |
| |
| i2c_debug_print("!!!! i2c device 0x%x probe\n", client->addr); |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) |
| goto exit; |
| |
| if (!(pclient_data = kzalloc(sizeof(struct maple_i2c_data), GFP_KERNEL))) |
| { |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| pclient_data->client.addr = client->addr; |
| pclient_data->client.adapter = client->adapter; |
| pclient_data->client.driver = client->driver; |
| pclient_data->client.flags = client->flags; |
| |
| i2c_set_clientdata(client, pclient_data); |
| |
| switch(client->addr) |
| { |
| case I2C_SW0_I2C_ADDR: |
| pclient_data_ar[SLAVE_SWITCH_70] = pclient_data; |
| slave_valid[SLAVE_SWITCH_70] = 1; |
| break; |
| case I2C_SW1_I2C_ADDR: |
| pclient_data_ar[SLAVE_SWITCH_71] = pclient_data; |
| slave_valid[SLAVE_SWITCH_71] = 1; |
| break; |
| case I2C_SW2_I2C_ADDR: |
| pclient_data_ar[SLAVE_SWITCH_72] = pclient_data; |
| slave_valid[SLAVE_SWITCH_72] = 1; |
| break; |
| case I2C_SW3_I2C_ADDR: |
| pclient_data_ar[SLAVE_SWITCH_73] = pclient_data; |
| slave_valid[SLAVE_SWITCH_73] = 1; |
| break; |
| case I2C_SW4_I2C_ADDR: |
| pclient_data_ar[SLAVE_SWITCH_74] = pclient_data; |
| slave_valid[SLAVE_SWITCH_74] = 1; |
| break; |
| case SFP_I2C_ADDR1: |
| pclient_data_ar[SLAVE_SFP_50] = pclient_data; |
| slave_valid[SLAVE_SFP_50] = 1; |
| break; |
| case SFP_I2C_ADDR2: |
| pclient_data_ar[SLAVE_SFP_51] = pclient_data; |
| slave_valid[SLAVE_SFP_51] = 1; |
| break; |
| case FPGA_I2C_ADDR: |
| pclient_data_ar[SLAVE_FPGA_40] = pclient_data; |
| slave_valid[SLAVE_FPGA_40] = 1; |
| break; |
| case PON_DPLL_I2C_ADDR: |
| pclient_data_ar[SLAVE_PON_DPLL_68] = pclient_data; |
| slave_valid[SLAVE_PON_DPLL_68] = 1; |
| break; |
| case PM_DPLL_I2C_ADDR: |
| pclient_data_ar[SLAVE_PM_DPLL_6A] = pclient_data; |
| slave_valid[SLAVE_PM_DPLL_6A] = 1; |
| break; |
| case CXP_R_I2C_ADDR: |
| pclient_data_ar[SLAVE_CXP_R_54] = pclient_data; |
| slave_valid[SLAVE_CXP_R_54] = 1; |
| break; |
| case PCIE_SW_I2C_ADDR: |
| pclient_data_ar[SLAVE_PCIE_SW_3C] = pclient_data; |
| slave_valid[SLAVE_PCIE_SW_3C] = 1; |
| break; |
| default: |
| i2c_debug_print("%s client addr out of range 0x%x\n", __FUNCTION__, client->addr); |
| goto exit_kfree; |
| } |
| |
| /* Create only once */ |
| if (i2c_proc_entry == NULL) |
| { |
| if (maple_i2c_dev_create_proc() < 0) |
| goto exit_kfree; |
| } |
| if (!is_cdev_add) |
| { |
| if (maple_i2c_dev_create_chrdev() < 0) |
| goto exit_kfree; |
| } |
| ++i2c_num_clients; |
| |
| return 0; |
| |
| exit_kfree: |
| kfree(pclient_data); |
| exit: |
| return err; |
| } |
| |
| static int maple_i2c_dev_remove(struct i2c_client *client) |
| { |
| kfree(i2c_get_clientdata(client)); |
| --i2c_num_clients; |
| if (!i2c_num_clients) |
| { |
| if (i2c_proc_entry != NULL) |
| { |
| remove_proc_entry(PROC_ENTRY_NAME, i2c_proc_dir); |
| i2c_proc_entry = NULL; |
| } |
| if (i2c_proc_dir != NULL) |
| { |
| remove_proc_entry(PROC_DIR_NAME, NULL); |
| i2c_proc_dir = NULL; |
| } |
| if (is_cdev_add) |
| { |
| cdev_del(&maple_i2c_chrdev_cdev); |
| is_cdev_add = 0; |
| } |
| if (is_chrdev_reg) |
| { |
| unregister_chrdev_region(MKDEV(maple_i2c_chrdev_major, 0), 1); |
| is_chrdev_reg = 0; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static struct i2c_driver maple_i2c_dev_driver = { |
| .class = ~0, |
| .driver = { |
| .name = "maple_i2c", |
| }, |
| .probe = maple_i2c_dev_probe, |
| .remove = maple_i2c_dev_remove, |
| .id_table = maple_i2c_id_table, |
| .detect = maple_i2c_dev_detect, |
| .address_list = normal_i2c, |
| }; |
| |
| module_i2c_driver(maple_i2c_dev_driver); |
| |
| MODULE_DESCRIPTION("maple i2c devices"); |
| MODULE_LICENSE("GPL"); |
| |