| /* | |
| * 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> | |
| * | |
| * This program is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 as published by | |
| * the Free Software Foundation | |
| */ | |
| #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"); |