|  | // 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 = kzalloc(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 - 1); | 
|  | 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"); |