| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Silicon Labs C2 port core Linux support |
| * |
| * Copyright (c) 2007 Rodolfo Giometti <giometti@linux.it> |
| * Copyright (c) 2007 Eurotech S.p.A. <info@eurotech.it> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/device.h> |
| #include <linux/errno.h> |
| #include <linux/err.h> |
| #include <linux/kernel.h> |
| #include <linux/ctype.h> |
| #include <linux/delay.h> |
| #include <linux/idr.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| |
| #include <linux/c2port.h> |
| |
| #define DRIVER_NAME "c2port" |
| #define DRIVER_VERSION "0.51.0" |
| |
| static DEFINE_SPINLOCK(c2port_idr_lock); |
| static DEFINE_IDR(c2port_idr); |
| |
| /* |
| * Local variables |
| */ |
| |
| static struct class *c2port_class; |
| |
| /* |
| * C2 registers & commands defines |
| */ |
| |
| /* C2 registers */ |
| #define C2PORT_DEVICEID 0x00 |
| #define C2PORT_REVID 0x01 |
| #define C2PORT_FPCTL 0x02 |
| #define C2PORT_FPDAT 0xB4 |
| |
| /* C2 interface commands */ |
| #define C2PORT_GET_VERSION 0x01 |
| #define C2PORT_DEVICE_ERASE 0x03 |
| #define C2PORT_BLOCK_READ 0x06 |
| #define C2PORT_BLOCK_WRITE 0x07 |
| #define C2PORT_PAGE_ERASE 0x08 |
| |
| /* C2 status return codes */ |
| #define C2PORT_INVALID_COMMAND 0x00 |
| #define C2PORT_COMMAND_FAILED 0x02 |
| #define C2PORT_COMMAND_OK 0x0d |
| |
| /* |
| * C2 port low level signal managements |
| */ |
| |
| static void c2port_reset(struct c2port_device *dev) |
| { |
| struct c2port_ops *ops = dev->ops; |
| |
| /* To reset the device we have to keep clock line low for at least |
| * 20us. |
| */ |
| local_irq_disable(); |
| ops->c2ck_set(dev, 0); |
| udelay(25); |
| ops->c2ck_set(dev, 1); |
| local_irq_enable(); |
| |
| udelay(1); |
| } |
| |
| static void c2port_strobe_ck(struct c2port_device *dev) |
| { |
| struct c2port_ops *ops = dev->ops; |
| |
| /* During hi-low-hi transition we disable local IRQs to avoid |
| * interructions since C2 port specification says that it must be |
| * shorter than 5us, otherwise the microcontroller may consider |
| * it as a reset signal! |
| */ |
| local_irq_disable(); |
| ops->c2ck_set(dev, 0); |
| udelay(1); |
| ops->c2ck_set(dev, 1); |
| local_irq_enable(); |
| |
| udelay(1); |
| } |
| |
| /* |
| * C2 port basic functions |
| */ |
| |
| static void c2port_write_ar(struct c2port_device *dev, u8 addr) |
| { |
| struct c2port_ops *ops = dev->ops; |
| int i; |
| |
| /* START field */ |
| c2port_strobe_ck(dev); |
| |
| /* INS field (11b, LSB first) */ |
| ops->c2d_dir(dev, 0); |
| ops->c2d_set(dev, 1); |
| c2port_strobe_ck(dev); |
| ops->c2d_set(dev, 1); |
| c2port_strobe_ck(dev); |
| |
| /* ADDRESS field */ |
| for (i = 0; i < 8; i++) { |
| ops->c2d_set(dev, addr & 0x01); |
| c2port_strobe_ck(dev); |
| |
| addr >>= 1; |
| } |
| |
| /* STOP field */ |
| ops->c2d_dir(dev, 1); |
| c2port_strobe_ck(dev); |
| } |
| |
| static int c2port_read_ar(struct c2port_device *dev, u8 *addr) |
| { |
| struct c2port_ops *ops = dev->ops; |
| int i; |
| |
| /* START field */ |
| c2port_strobe_ck(dev); |
| |
| /* INS field (10b, LSB first) */ |
| ops->c2d_dir(dev, 0); |
| ops->c2d_set(dev, 0); |
| c2port_strobe_ck(dev); |
| ops->c2d_set(dev, 1); |
| c2port_strobe_ck(dev); |
| |
| /* ADDRESS field */ |
| ops->c2d_dir(dev, 1); |
| *addr = 0; |
| for (i = 0; i < 8; i++) { |
| *addr >>= 1; /* shift in 8-bit ADDRESS field LSB first */ |
| |
| c2port_strobe_ck(dev); |
| if (ops->c2d_get(dev)) |
| *addr |= 0x80; |
| } |
| |
| /* STOP field */ |
| c2port_strobe_ck(dev); |
| |
| return 0; |
| } |
| |
| static int c2port_write_dr(struct c2port_device *dev, u8 data) |
| { |
| struct c2port_ops *ops = dev->ops; |
| int timeout, i; |
| |
| /* START field */ |
| c2port_strobe_ck(dev); |
| |
| /* INS field (01b, LSB first) */ |
| ops->c2d_dir(dev, 0); |
| ops->c2d_set(dev, 1); |
| c2port_strobe_ck(dev); |
| ops->c2d_set(dev, 0); |
| c2port_strobe_ck(dev); |
| |
| /* LENGTH field (00b, LSB first -> 1 byte) */ |
| ops->c2d_set(dev, 0); |
| c2port_strobe_ck(dev); |
| ops->c2d_set(dev, 0); |
| c2port_strobe_ck(dev); |
| |
| /* DATA field */ |
| for (i = 0; i < 8; i++) { |
| ops->c2d_set(dev, data & 0x01); |
| c2port_strobe_ck(dev); |
| |
| data >>= 1; |
| } |
| |
| /* WAIT field */ |
| ops->c2d_dir(dev, 1); |
| timeout = 20; |
| do { |
| c2port_strobe_ck(dev); |
| if (ops->c2d_get(dev)) |
| break; |
| |
| udelay(1); |
| } while (--timeout > 0); |
| if (timeout == 0) |
| return -EIO; |
| |
| /* STOP field */ |
| c2port_strobe_ck(dev); |
| |
| return 0; |
| } |
| |
| static int c2port_read_dr(struct c2port_device *dev, u8 *data) |
| { |
| struct c2port_ops *ops = dev->ops; |
| int timeout, i; |
| |
| /* START field */ |
| c2port_strobe_ck(dev); |
| |
| /* INS field (00b, LSB first) */ |
| ops->c2d_dir(dev, 0); |
| ops->c2d_set(dev, 0); |
| c2port_strobe_ck(dev); |
| ops->c2d_set(dev, 0); |
| c2port_strobe_ck(dev); |
| |
| /* LENGTH field (00b, LSB first -> 1 byte) */ |
| ops->c2d_set(dev, 0); |
| c2port_strobe_ck(dev); |
| ops->c2d_set(dev, 0); |
| c2port_strobe_ck(dev); |
| |
| /* WAIT field */ |
| ops->c2d_dir(dev, 1); |
| timeout = 20; |
| do { |
| c2port_strobe_ck(dev); |
| if (ops->c2d_get(dev)) |
| break; |
| |
| udelay(1); |
| } while (--timeout > 0); |
| if (timeout == 0) |
| return -EIO; |
| |
| /* DATA field */ |
| *data = 0; |
| for (i = 0; i < 8; i++) { |
| *data >>= 1; /* shift in 8-bit DATA field LSB first */ |
| |
| c2port_strobe_ck(dev); |
| if (ops->c2d_get(dev)) |
| *data |= 0x80; |
| } |
| |
| /* STOP field */ |
| c2port_strobe_ck(dev); |
| |
| return 0; |
| } |
| |
| static int c2port_poll_in_busy(struct c2port_device *dev) |
| { |
| u8 addr; |
| int ret, timeout = 20; |
| |
| do { |
| ret = (c2port_read_ar(dev, &addr)); |
| if (ret < 0) |
| return -EIO; |
| |
| if (!(addr & 0x02)) |
| break; |
| |
| udelay(1); |
| } while (--timeout > 0); |
| if (timeout == 0) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| static int c2port_poll_out_ready(struct c2port_device *dev) |
| { |
| u8 addr; |
| int ret, timeout = 10000; /* erase flash needs long time... */ |
| |
| do { |
| ret = (c2port_read_ar(dev, &addr)); |
| if (ret < 0) |
| return -EIO; |
| |
| if (addr & 0x01) |
| break; |
| |
| udelay(1); |
| } while (--timeout > 0); |
| if (timeout == 0) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| /* |
| * sysfs methods |
| */ |
| |
| static ssize_t c2port_show_name(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%s\n", c2dev->name); |
| } |
| static DEVICE_ATTR(name, 0444, c2port_show_name, NULL); |
| |
| static ssize_t c2port_show_flash_blocks_num(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| struct c2port_ops *ops = c2dev->ops; |
| |
| return sprintf(buf, "%d\n", ops->blocks_num); |
| } |
| static DEVICE_ATTR(flash_blocks_num, 0444, c2port_show_flash_blocks_num, NULL); |
| |
| static ssize_t c2port_show_flash_block_size(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| struct c2port_ops *ops = c2dev->ops; |
| |
| return sprintf(buf, "%d\n", ops->block_size); |
| } |
| static DEVICE_ATTR(flash_block_size, 0444, c2port_show_flash_block_size, NULL); |
| |
| static ssize_t c2port_show_flash_size(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| struct c2port_ops *ops = c2dev->ops; |
| |
| return sprintf(buf, "%d\n", ops->blocks_num * ops->block_size); |
| } |
| static DEVICE_ATTR(flash_size, 0444, c2port_show_flash_size, NULL); |
| |
| static ssize_t access_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%d\n", c2dev->access); |
| } |
| |
| static ssize_t access_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| struct c2port_ops *ops = c2dev->ops; |
| int status, ret; |
| |
| ret = sscanf(buf, "%d", &status); |
| if (ret != 1) |
| return -EINVAL; |
| |
| mutex_lock(&c2dev->mutex); |
| |
| c2dev->access = !!status; |
| |
| /* If access is "on" clock should be HIGH _before_ setting the line |
| * as output and data line should be set as INPUT anyway */ |
| if (c2dev->access) |
| ops->c2ck_set(c2dev, 1); |
| ops->access(c2dev, c2dev->access); |
| if (c2dev->access) |
| ops->c2d_dir(c2dev, 1); |
| |
| mutex_unlock(&c2dev->mutex); |
| |
| return count; |
| } |
| static DEVICE_ATTR_RW(access); |
| |
| static ssize_t c2port_store_reset(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| |
| /* Check the device access status */ |
| if (!c2dev->access) |
| return -EBUSY; |
| |
| mutex_lock(&c2dev->mutex); |
| |
| c2port_reset(c2dev); |
| c2dev->flash_access = 0; |
| |
| mutex_unlock(&c2dev->mutex); |
| |
| return count; |
| } |
| static DEVICE_ATTR(reset, 0200, NULL, c2port_store_reset); |
| |
| static ssize_t __c2port_show_dev_id(struct c2port_device *dev, char *buf) |
| { |
| u8 data; |
| int ret; |
| |
| /* Select DEVICEID register for C2 data register accesses */ |
| c2port_write_ar(dev, C2PORT_DEVICEID); |
| |
| /* Read and return the device ID register */ |
| ret = c2port_read_dr(dev, &data); |
| if (ret < 0) |
| return ret; |
| |
| return sprintf(buf, "%d\n", data); |
| } |
| |
| static ssize_t c2port_show_dev_id(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| ssize_t ret; |
| |
| /* Check the device access status */ |
| if (!c2dev->access) |
| return -EBUSY; |
| |
| mutex_lock(&c2dev->mutex); |
| ret = __c2port_show_dev_id(c2dev, buf); |
| mutex_unlock(&c2dev->mutex); |
| |
| if (ret < 0) |
| dev_err(dev, "cannot read from %s\n", c2dev->name); |
| |
| return ret; |
| } |
| static DEVICE_ATTR(dev_id, 0444, c2port_show_dev_id, NULL); |
| |
| static ssize_t __c2port_show_rev_id(struct c2port_device *dev, char *buf) |
| { |
| u8 data; |
| int ret; |
| |
| /* Select REVID register for C2 data register accesses */ |
| c2port_write_ar(dev, C2PORT_REVID); |
| |
| /* Read and return the revision ID register */ |
| ret = c2port_read_dr(dev, &data); |
| if (ret < 0) |
| return ret; |
| |
| return sprintf(buf, "%d\n", data); |
| } |
| |
| static ssize_t c2port_show_rev_id(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| ssize_t ret; |
| |
| /* Check the device access status */ |
| if (!c2dev->access) |
| return -EBUSY; |
| |
| mutex_lock(&c2dev->mutex); |
| ret = __c2port_show_rev_id(c2dev, buf); |
| mutex_unlock(&c2dev->mutex); |
| |
| if (ret < 0) |
| dev_err(c2dev->dev, "cannot read from %s\n", c2dev->name); |
| |
| return ret; |
| } |
| static DEVICE_ATTR(rev_id, 0444, c2port_show_rev_id, NULL); |
| |
| static ssize_t c2port_show_flash_access(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%d\n", c2dev->flash_access); |
| } |
| |
| static ssize_t __c2port_store_flash_access(struct c2port_device *dev, |
| int status) |
| { |
| int ret; |
| |
| /* Check the device access status */ |
| if (!dev->access) |
| return -EBUSY; |
| |
| dev->flash_access = !!status; |
| |
| /* If flash_access is off we have nothing to do... */ |
| if (dev->flash_access == 0) |
| return 0; |
| |
| /* Target the C2 flash programming control register for C2 data |
| * register access */ |
| c2port_write_ar(dev, C2PORT_FPCTL); |
| |
| /* Write the first keycode to enable C2 Flash programming */ |
| ret = c2port_write_dr(dev, 0x02); |
| if (ret < 0) |
| return ret; |
| |
| /* Write the second keycode to enable C2 Flash programming */ |
| ret = c2port_write_dr(dev, 0x01); |
| if (ret < 0) |
| return ret; |
| |
| /* Delay for at least 20ms to ensure the target is ready for |
| * C2 flash programming */ |
| mdelay(25); |
| |
| return 0; |
| } |
| |
| static ssize_t c2port_store_flash_access(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| int status; |
| ssize_t ret; |
| |
| ret = sscanf(buf, "%d", &status); |
| if (ret != 1) |
| return -EINVAL; |
| |
| mutex_lock(&c2dev->mutex); |
| ret = __c2port_store_flash_access(c2dev, status); |
| mutex_unlock(&c2dev->mutex); |
| |
| if (ret < 0) { |
| dev_err(c2dev->dev, "cannot enable %s flash programming\n", |
| c2dev->name); |
| return ret; |
| } |
| |
| return count; |
| } |
| static DEVICE_ATTR(flash_access, 0644, c2port_show_flash_access, |
| c2port_store_flash_access); |
| |
| static ssize_t __c2port_write_flash_erase(struct c2port_device *dev) |
| { |
| u8 status; |
| int ret; |
| |
| /* Target the C2 flash programming data register for C2 data register |
| * access. |
| */ |
| c2port_write_ar(dev, C2PORT_FPDAT); |
| |
| /* Send device erase command */ |
| c2port_write_dr(dev, C2PORT_DEVICE_ERASE); |
| |
| /* Wait for input acknowledge */ |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Should check status before starting FLASH access sequence */ |
| |
| /* Wait for status information */ |
| ret = c2port_poll_out_ready(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Read flash programming interface status */ |
| ret = c2port_read_dr(dev, &status); |
| if (ret < 0) |
| return ret; |
| if (status != C2PORT_COMMAND_OK) |
| return -EBUSY; |
| |
| /* Send a three-byte arming sequence to enable the device erase. |
| * If the sequence is not received correctly, the command will be |
| * ignored. |
| * Sequence is: 0xde, 0xad, 0xa5. |
| */ |
| c2port_write_dr(dev, 0xde); |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| c2port_write_dr(dev, 0xad); |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| c2port_write_dr(dev, 0xa5); |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| ret = c2port_poll_out_ready(dev); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| static ssize_t c2port_store_flash_erase(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(dev); |
| int ret; |
| |
| /* Check the device and flash access status */ |
| if (!c2dev->access || !c2dev->flash_access) |
| return -EBUSY; |
| |
| mutex_lock(&c2dev->mutex); |
| ret = __c2port_write_flash_erase(c2dev); |
| mutex_unlock(&c2dev->mutex); |
| |
| if (ret < 0) { |
| dev_err(c2dev->dev, "cannot erase %s flash\n", c2dev->name); |
| return ret; |
| } |
| |
| return count; |
| } |
| static DEVICE_ATTR(flash_erase, 0200, NULL, c2port_store_flash_erase); |
| |
| static ssize_t __c2port_read_flash_data(struct c2port_device *dev, |
| char *buffer, loff_t offset, size_t count) |
| { |
| struct c2port_ops *ops = dev->ops; |
| u8 status, nread = 128; |
| int i, ret; |
| |
| /* Check for flash end */ |
| if (offset >= ops->block_size * ops->blocks_num) |
| return 0; |
| |
| if (ops->block_size * ops->blocks_num - offset < nread) |
| nread = ops->block_size * ops->blocks_num - offset; |
| if (count < nread) |
| nread = count; |
| if (nread == 0) |
| return nread; |
| |
| /* Target the C2 flash programming data register for C2 data register |
| * access */ |
| c2port_write_ar(dev, C2PORT_FPDAT); |
| |
| /* Send flash block read command */ |
| c2port_write_dr(dev, C2PORT_BLOCK_READ); |
| |
| /* Wait for input acknowledge */ |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Should check status before starting FLASH access sequence */ |
| |
| /* Wait for status information */ |
| ret = c2port_poll_out_ready(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Read flash programming interface status */ |
| ret = c2port_read_dr(dev, &status); |
| if (ret < 0) |
| return ret; |
| if (status != C2PORT_COMMAND_OK) |
| return -EBUSY; |
| |
| /* Send address high byte */ |
| c2port_write_dr(dev, offset >> 8); |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Send address low byte */ |
| c2port_write_dr(dev, offset & 0x00ff); |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Send address block size */ |
| c2port_write_dr(dev, nread); |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Should check status before reading FLASH block */ |
| |
| /* Wait for status information */ |
| ret = c2port_poll_out_ready(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Read flash programming interface status */ |
| ret = c2port_read_dr(dev, &status); |
| if (ret < 0) |
| return ret; |
| if (status != C2PORT_COMMAND_OK) |
| return -EBUSY; |
| |
| /* Read flash block */ |
| for (i = 0; i < nread; i++) { |
| ret = c2port_poll_out_ready(dev); |
| if (ret < 0) |
| return ret; |
| |
| ret = c2port_read_dr(dev, buffer+i); |
| if (ret < 0) |
| return ret; |
| } |
| |
| return nread; |
| } |
| |
| static ssize_t c2port_read_flash_data(struct file *filp, struct kobject *kobj, |
| struct bin_attribute *attr, |
| char *buffer, loff_t offset, size_t count) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(kobj_to_dev(kobj)); |
| ssize_t ret; |
| |
| /* Check the device and flash access status */ |
| if (!c2dev->access || !c2dev->flash_access) |
| return -EBUSY; |
| |
| mutex_lock(&c2dev->mutex); |
| ret = __c2port_read_flash_data(c2dev, buffer, offset, count); |
| mutex_unlock(&c2dev->mutex); |
| |
| if (ret < 0) |
| dev_err(c2dev->dev, "cannot read %s flash\n", c2dev->name); |
| |
| return ret; |
| } |
| |
| static ssize_t __c2port_write_flash_data(struct c2port_device *dev, |
| char *buffer, loff_t offset, size_t count) |
| { |
| struct c2port_ops *ops = dev->ops; |
| u8 status, nwrite = 128; |
| int i, ret; |
| |
| if (nwrite > count) |
| nwrite = count; |
| if (ops->block_size * ops->blocks_num - offset < nwrite) |
| nwrite = ops->block_size * ops->blocks_num - offset; |
| |
| /* Check for flash end */ |
| if (offset >= ops->block_size * ops->blocks_num) |
| return -EINVAL; |
| |
| /* Target the C2 flash programming data register for C2 data register |
| * access */ |
| c2port_write_ar(dev, C2PORT_FPDAT); |
| |
| /* Send flash block write command */ |
| c2port_write_dr(dev, C2PORT_BLOCK_WRITE); |
| |
| /* Wait for input acknowledge */ |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Should check status before starting FLASH access sequence */ |
| |
| /* Wait for status information */ |
| ret = c2port_poll_out_ready(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Read flash programming interface status */ |
| ret = c2port_read_dr(dev, &status); |
| if (ret < 0) |
| return ret; |
| if (status != C2PORT_COMMAND_OK) |
| return -EBUSY; |
| |
| /* Send address high byte */ |
| c2port_write_dr(dev, offset >> 8); |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Send address low byte */ |
| c2port_write_dr(dev, offset & 0x00ff); |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Send address block size */ |
| c2port_write_dr(dev, nwrite); |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Should check status before writing FLASH block */ |
| |
| /* Wait for status information */ |
| ret = c2port_poll_out_ready(dev); |
| if (ret < 0) |
| return ret; |
| |
| /* Read flash programming interface status */ |
| ret = c2port_read_dr(dev, &status); |
| if (ret < 0) |
| return ret; |
| if (status != C2PORT_COMMAND_OK) |
| return -EBUSY; |
| |
| /* Write flash block */ |
| for (i = 0; i < nwrite; i++) { |
| ret = c2port_write_dr(dev, *(buffer+i)); |
| if (ret < 0) |
| return ret; |
| |
| ret = c2port_poll_in_busy(dev); |
| if (ret < 0) |
| return ret; |
| |
| } |
| |
| /* Wait for last flash write to complete */ |
| ret = c2port_poll_out_ready(dev); |
| if (ret < 0) |
| return ret; |
| |
| return nwrite; |
| } |
| |
| static ssize_t c2port_write_flash_data(struct file *filp, struct kobject *kobj, |
| struct bin_attribute *attr, |
| char *buffer, loff_t offset, size_t count) |
| { |
| struct c2port_device *c2dev = dev_get_drvdata(kobj_to_dev(kobj)); |
| int ret; |
| |
| /* Check the device access status */ |
| if (!c2dev->access || !c2dev->flash_access) |
| return -EBUSY; |
| |
| mutex_lock(&c2dev->mutex); |
| ret = __c2port_write_flash_data(c2dev, buffer, offset, count); |
| mutex_unlock(&c2dev->mutex); |
| |
| if (ret < 0) |
| dev_err(c2dev->dev, "cannot write %s flash\n", c2dev->name); |
| |
| return ret; |
| } |
| /* size is computed at run-time */ |
| static BIN_ATTR(flash_data, 0644, c2port_read_flash_data, |
| c2port_write_flash_data, 0); |
| |
| /* |
| * Class attributes |
| */ |
| static struct attribute *c2port_attrs[] = { |
| &dev_attr_name.attr, |
| &dev_attr_flash_blocks_num.attr, |
| &dev_attr_flash_block_size.attr, |
| &dev_attr_flash_size.attr, |
| &dev_attr_access.attr, |
| &dev_attr_reset.attr, |
| &dev_attr_dev_id.attr, |
| &dev_attr_rev_id.attr, |
| &dev_attr_flash_access.attr, |
| &dev_attr_flash_erase.attr, |
| NULL, |
| }; |
| |
| static struct bin_attribute *c2port_bin_attrs[] = { |
| &bin_attr_flash_data, |
| NULL, |
| }; |
| |
| static const struct attribute_group c2port_group = { |
| .attrs = c2port_attrs, |
| .bin_attrs = c2port_bin_attrs, |
| }; |
| |
| static const struct attribute_group *c2port_groups[] = { |
| &c2port_group, |
| NULL, |
| }; |
| |
| /* |
| * Exported functions |
| */ |
| |
| struct c2port_device *c2port_device_register(char *name, |
| struct c2port_ops *ops, void *devdata) |
| { |
| struct c2port_device *c2dev; |
| int ret; |
| |
| if (unlikely(!ops) || unlikely(!ops->access) || \ |
| unlikely(!ops->c2d_dir) || unlikely(!ops->c2ck_set) || \ |
| unlikely(!ops->c2d_get) || unlikely(!ops->c2d_set)) |
| return ERR_PTR(-EINVAL); |
| |
| c2dev = kmalloc(sizeof(struct c2port_device), GFP_KERNEL); |
| if (unlikely(!c2dev)) |
| return ERR_PTR(-ENOMEM); |
| |
| idr_preload(GFP_KERNEL); |
| spin_lock_irq(&c2port_idr_lock); |
| ret = idr_alloc(&c2port_idr, c2dev, 0, 0, GFP_NOWAIT); |
| spin_unlock_irq(&c2port_idr_lock); |
| idr_preload_end(); |
| |
| if (ret < 0) |
| goto error_idr_alloc; |
| c2dev->id = ret; |
| |
| bin_attr_flash_data.size = ops->blocks_num * ops->block_size; |
| |
| c2dev->dev = device_create(c2port_class, NULL, 0, c2dev, |
| "c2port%d", c2dev->id); |
| if (IS_ERR(c2dev->dev)) { |
| ret = PTR_ERR(c2dev->dev); |
| goto error_device_create; |
| } |
| dev_set_drvdata(c2dev->dev, c2dev); |
| |
| strncpy(c2dev->name, name, C2PORT_NAME_LEN); |
| c2dev->ops = ops; |
| mutex_init(&c2dev->mutex); |
| |
| /* By default C2 port access is off */ |
| c2dev->access = c2dev->flash_access = 0; |
| ops->access(c2dev, 0); |
| |
| dev_info(c2dev->dev, "C2 port %s added\n", name); |
| dev_info(c2dev->dev, "%s flash has %d blocks x %d bytes " |
| "(%d bytes total)\n", |
| name, ops->blocks_num, ops->block_size, |
| ops->blocks_num * ops->block_size); |
| |
| return c2dev; |
| |
| error_device_create: |
| spin_lock_irq(&c2port_idr_lock); |
| idr_remove(&c2port_idr, c2dev->id); |
| spin_unlock_irq(&c2port_idr_lock); |
| |
| error_idr_alloc: |
| kfree(c2dev); |
| |
| return ERR_PTR(ret); |
| } |
| EXPORT_SYMBOL(c2port_device_register); |
| |
| void c2port_device_unregister(struct c2port_device *c2dev) |
| { |
| if (!c2dev) |
| return; |
| |
| dev_info(c2dev->dev, "C2 port %s removed\n", c2dev->name); |
| |
| spin_lock_irq(&c2port_idr_lock); |
| idr_remove(&c2port_idr, c2dev->id); |
| spin_unlock_irq(&c2port_idr_lock); |
| |
| device_destroy(c2port_class, c2dev->id); |
| |
| kfree(c2dev); |
| } |
| EXPORT_SYMBOL(c2port_device_unregister); |
| |
| /* |
| * Module stuff |
| */ |
| |
| static int __init c2port_init(void) |
| { |
| printk(KERN_INFO "Silicon Labs C2 port support v. " DRIVER_VERSION |
| " - (C) 2007 Rodolfo Giometti\n"); |
| |
| c2port_class = class_create(THIS_MODULE, "c2port"); |
| if (IS_ERR(c2port_class)) { |
| printk(KERN_ERR "c2port: failed to allocate class\n"); |
| return PTR_ERR(c2port_class); |
| } |
| c2port_class->dev_groups = c2port_groups; |
| |
| return 0; |
| } |
| |
| static void __exit c2port_exit(void) |
| { |
| class_destroy(c2port_class); |
| } |
| |
| module_init(c2port_init); |
| module_exit(c2port_exit); |
| |
| MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); |
| MODULE_DESCRIPTION("Silicon Labs C2 port support v. " DRIVER_VERSION); |
| MODULE_LICENSE("GPL"); |