| /* |
| * monza_x.c: driver for Impinj RFID chip |
| * |
| * (c) copyright 2013 intel corporation |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; version 2 |
| * of the License. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/mutex.h> |
| #include <linux/sysfs.h> |
| #include <linux/log2.h> |
| #include <linux/i2c.h> |
| #include <linux/miscdevice.h> |
| #include <linux/acpi.h> |
| #include <linux/fs.h> |
| #include <linux/uaccess.h> |
| |
| #define MONZAX_2K_BYTE_LEN 336 |
| #define MONZAX_8K_BYTE_LEN 1088 |
| #define MONZAX_KBUF_MAX 1088 |
| |
| #define MONZAX_2K_CLASSID_OFF 328 |
| #define MONZAX_8K_CLASSID_OFF 40 |
| #define MONZAX_GEN2_CLASSID 0xE2 |
| |
| enum slave_addr_num { |
| MONZAX_8K_ADDR_NUM = 1, |
| MONZAX_2K_ADDR_NUM |
| }; |
| /* |
| * word/2word write will take time before next write, |
| * set 100ms threshold for safe. |
| */ |
| #define WRITE_TIMEOUT 100 |
| |
| struct monza_data { |
| struct mutex lock; |
| struct bin_attribute bin; |
| |
| u8 *writebuf; |
| unsigned write_max; |
| unsigned num_addr; |
| |
| struct miscdevice miscdev; |
| /* monzax_2k has 2 i2c slave addr */ |
| struct i2c_client *client[2]; |
| }; |
| |
| static struct i2c_client *monza_translate_offset(struct monza_data *monza, |
| unsigned *offset) |
| { |
| unsigned i = 0; |
| |
| if (monza->num_addr == MONZAX_2K_ADDR_NUM) { |
| i = *offset >> 8; |
| *offset &= 0xff; |
| } |
| |
| return monza->client[i]; |
| } |
| |
| static ssize_t monza_eeprom_read(struct monza_data *monza, char *buf, |
| unsigned offset, size_t count) |
| { |
| struct i2c_client *client; |
| struct i2c_msg msg[2]; |
| u8 msgbuf[2]; |
| int status, i = 0; |
| |
| memset(msg, 0, sizeof(msg)); |
| client = monza_translate_offset(monza, &offset); |
| |
| /* for monzax 8k, eeprom offset is 16bit/2byt mode */ |
| if (monza->num_addr == MONZAX_8K_ADDR_NUM) |
| msgbuf[i++] = offset >> 8; |
| msgbuf[i++] = offset; |
| |
| msg[0].addr = client->addr; |
| msg[0].buf = msgbuf; |
| msg[0].len = i; |
| |
| msg[1].addr = client->addr; |
| msg[1].flags = I2C_M_RD; |
| msg[1].buf = buf; |
| msg[1].len = count; |
| |
| status = i2c_transfer(client->adapter, msg, 2); |
| if (status == 2) |
| status = count; |
| dev_dbg(&client->dev, "read %u@%d --> %d\n", |
| count, offset, status); |
| return status; |
| } |
| |
| static ssize_t monza_read(struct monza_data *monza, |
| char *buf, loff_t off, size_t count) |
| { |
| ssize_t retval = 0; |
| unsigned long timeout, read_time; |
| /* |
| * Read data from chip, protecting against concurrent updates |
| * from this host, but not from other I2C masters. |
| */ |
| mutex_lock(&monza->lock); |
| |
| while (count) { |
| ssize_t status; |
| |
| /* |
| * Reads fail if the previous write didn't complete yet. We may |
| * loop a few times until this one succeeds. |
| */ |
| timeout = jiffies + msecs_to_jiffies(WRITE_TIMEOUT); |
| do { |
| read_time = jiffies; |
| status = monza_eeprom_read(monza, buf, off, count); |
| if (status == count) |
| break; |
| usleep_range(2000, 2050); |
| } while (time_before(read_time, timeout)); |
| |
| /* exception handle */ |
| if (status < 0) { |
| if (retval == 0) |
| retval = status; |
| break; |
| } |
| |
| buf += status; |
| off += status; |
| count -= status; |
| retval += status; |
| } |
| |
| mutex_unlock(&monza->lock); |
| |
| return retval; |
| } |
| |
| static ssize_t monza_bin_read(struct file *filp, struct kobject *kobj, |
| struct bin_attribute *attr, |
| char *buf, loff_t off, size_t count) |
| { |
| struct monza_data *monza; |
| |
| monza = dev_get_drvdata(container_of(kobj, struct device, kobj)); |
| return monza_read(monza, buf, off, count); |
| } |
| |
| static ssize_t monza_eeprom_write(struct monza_data *monza, const char *buf, |
| unsigned offset, size_t count) |
| { |
| struct i2c_client *client; |
| struct i2c_msg msg; |
| int status, i = 0; |
| |
| /* Get corresponding I2C address and adjust offset */ |
| client = monza_translate_offset(monza, &offset); |
| |
| msg.addr = client->addr; |
| msg.flags = 0; |
| msg.buf = monza->writebuf; |
| /* for monzax 8k, eeprom offset is 16bit/2byt mode */ |
| if (monza->num_addr == MONZAX_8K_ADDR_NUM) |
| msg.buf[i++] = offset >> 8; |
| msg.buf[i++] = offset; |
| memcpy(&msg.buf[i], buf, count); |
| msg.len = i + count; |
| |
| status = i2c_transfer(client->adapter, &msg, 1); |
| dev_dbg(&client->dev, "write %u@%d --> %d\n", |
| count, offset, status); |
| if (status == 1) |
| status = count; |
| return status; |
| } |
| |
| static ssize_t monza_write(struct monza_data *monza, const char *buf, |
| loff_t off, size_t count) |
| { |
| ssize_t retval = 0; |
| unsigned long timeout, write_time; |
| |
| if ((off % 2 != 0) || (count % 2 != 0)) { |
| dev_err(&monza->client[0]->dev, "word boundary error\n"); |
| return 0; |
| } |
| /* |
| * Write data to chip, protecting against concurrent updates |
| * from this host, but not from other I2C masters. |
| */ |
| mutex_lock(&monza->lock); |
| |
| while (count) { |
| ssize_t status; |
| size_t cnt; |
| /* write_max is at most a 2word/4byte */ |
| if (count > monza->write_max) |
| cnt = monza->write_max; |
| else |
| cnt = count; |
| /* |
| * Writes fail if the previous one didn't complete yet. We may |
| * loop a few times until this one succeeds. |
| */ |
| timeout = jiffies + msecs_to_jiffies(WRITE_TIMEOUT); |
| do { |
| write_time = jiffies; |
| status = monza_eeprom_write(monza, buf, off, cnt); |
| if (status == cnt) |
| break; |
| usleep_range(2000, 2050); |
| } while (time_before(write_time, timeout)); |
| |
| /* exception handle */ |
| if (status < 0) { |
| if (retval == 0) |
| retval = status; |
| break; |
| } |
| |
| buf += status; |
| off += status; |
| count -= status; |
| retval += status; |
| } |
| |
| mutex_unlock(&monza->lock); |
| |
| return retval; |
| } |
| |
| static ssize_t monza_bin_write(struct file *filp, struct kobject *kobj, |
| struct bin_attribute *attr, |
| char *buf, loff_t off, size_t count) |
| { |
| struct monza_data *monza; |
| |
| monza = dev_get_drvdata(container_of(kobj, struct device, kobj)); |
| return monza_write(monza, buf, off, count); |
| } |
| |
| static int monza_check_ids(struct monza_data *monza) |
| { |
| int status, off = MONZAX_2K_CLASSID_OFF; |
| unsigned char buf[2] = { 0 }; |
| |
| if (monza->num_addr == MONZAX_2K_ADDR_NUM) |
| off = MONZAX_2K_CLASSID_OFF; |
| else if (monza->num_addr == MONZAX_8K_ADDR_NUM) |
| off = MONZAX_8K_CLASSID_OFF; |
| |
| status = monza_read(monza, buf, off, 1); |
| if (status > 0 && buf[0] == MONZAX_GEN2_CLASSID) |
| return 0; |
| else |
| return -ENODEV; |
| } |
| |
| static int monza_misc_open(struct inode *inode, struct file *filp) |
| { |
| struct monza_data *monza = container_of(filp->private_data, |
| struct monza_data, miscdev); |
| filp->private_data = monza; |
| return 0; |
| } |
| |
| static int monza_misc_release(struct inode *inode, struct file *filp) |
| { |
| return 0; |
| } |
| |
| static ssize_t monza_misc_read(struct file *filp, char __user *ubuf, |
| size_t count, loff_t *pos) |
| { |
| struct monza_data *monza = filp->private_data; |
| u8 *kbuf; |
| ssize_t cnt; |
| |
| kbuf = kmalloc(MONZAX_KBUF_MAX, GFP_KERNEL); |
| if (kbuf == NULL) { |
| dev_err(&monza->client[0]->dev, "%s(%d): buf allocation failed\n", |
| __func__, __LINE__); |
| return -ENOMEM; |
| } |
| |
| count = min_t(size_t, MONZAX_KBUF_MAX - *pos, count); |
| cnt = monza_read(monza, kbuf, *pos, count); |
| if (cnt <= 0) |
| goto out; |
| |
| if (copy_to_user(ubuf, kbuf, cnt)) { |
| cnt = -EFAULT; |
| goto out; |
| } |
| *pos += cnt; |
| out: |
| kfree(kbuf); |
| return cnt; |
| } |
| |
| static ssize_t monza_misc_write(struct file *filp, const char __user *ubuf, |
| size_t count, loff_t *pos) |
| { |
| struct monza_data *monza = filp->private_data; |
| u8 *kbuf; |
| ssize_t cnt; |
| |
| kbuf = kmalloc(MONZAX_KBUF_MAX, GFP_KERNEL); |
| if (kbuf == NULL) { |
| dev_err(&monza->client[0]->dev, "%s(%d): buf allocation failed\n", |
| __func__, __LINE__); |
| return -ENOMEM; |
| } |
| |
| count = min_t(size_t, MONZAX_KBUF_MAX - *pos, count); |
| if (copy_from_user(kbuf, ubuf, count)) { |
| cnt = -EFAULT; |
| goto out; |
| } |
| |
| cnt = monza_write(monza, kbuf, *pos, count); |
| if (cnt <= 0) |
| goto out; |
| |
| *pos += count; |
| out: |
| kfree(kbuf); |
| return cnt; |
| } |
| |
| static const struct file_operations monza_misc_fops = { |
| .owner = THIS_MODULE, |
| .read = monza_misc_read, |
| .write = monza_misc_write, |
| .llseek = generic_file_llseek, |
| .open = monza_misc_open, |
| .release = monza_misc_release, |
| }; |
| |
| static const struct i2c_device_id i2c_monza_ids[] = { |
| { "MNZX2000", MONZAX_2K_ADDR_NUM }, |
| { "MNZX8000", MONZAX_8K_ADDR_NUM }, |
| { "IMPJ0003", MONZAX_8K_ADDR_NUM }, |
| { /* END OF LIST */ } |
| }; |
| MODULE_DEVICE_TABLE(i2c, i2c_monza_ids); |
| |
| static const struct acpi_device_id acpi_monza_ids[] = { |
| { "MNZX2000", MONZAX_2K_ADDR_NUM }, |
| { "MNZX8000", MONZAX_8K_ADDR_NUM }, |
| { "IMPJ0003", MONZAX_8K_ADDR_NUM }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(acpi, acpi_monza_ids); |
| |
| static int monza_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct monza_data *monza; |
| const struct acpi_device_id *aid; |
| int err; |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| dev_err(&client->dev, "client not i2c capable\n"); |
| err = -ENODEV; |
| goto err_out; |
| } |
| |
| monza = kzalloc(sizeof(struct monza_data), GFP_KERNEL); |
| if (!monza) { |
| err = -ENOMEM; |
| goto err_out; |
| } |
| |
| mutex_init(&monza->lock); |
| |
| if (id) |
| monza->num_addr = id->driver_data; |
| else { |
| /* acpi id detect */ |
| for (aid = acpi_monza_ids; aid->id[0]; aid++) |
| if (!strncmp(aid->id, client->name, strlen(aid->id))) { |
| monza->num_addr = aid->driver_data; |
| dev_info(&client->dev, "acpi id: %s\n", client->name); |
| } |
| } |
| if (!monza->num_addr) { |
| dev_err(&client->dev, "Invalid id driver data error.\n"); |
| err = -ENODEV; |
| goto err_struct; |
| } |
| |
| monza->client[0] = client; |
| /* use dummy device, since monzax-2k has 2 slave address */ |
| if (monza->num_addr == MONZAX_2K_ADDR_NUM) { |
| monza->client[1] = i2c_new_dummy(client->adapter, |
| client->addr + 1); |
| if (!monza->client[1]) { |
| dev_err(&client->dev, "address 0x%02x unavailable\n", |
| client->addr + 1); |
| err = -EADDRINUSE; |
| goto err_struct; |
| } |
| } |
| |
| /* identify the real chip and address */ |
| err = monza_check_ids(monza); |
| if (err) { |
| dev_err(&client->dev, " detect chip failure.\n"); |
| goto err_clients; |
| } |
| |
| /* buffer (data + address at the beginning) */ |
| monza->write_max = 4; |
| monza->writebuf = kmalloc(monza->write_max + 2, GFP_KERNEL); |
| if (!monza->writebuf) { |
| err = -ENOMEM; |
| goto err_clients; |
| } |
| |
| /* |
| * Export the EEPROM bytes through sysfs, since that's convenient. |
| * By default, only root should see the data (maybe passwords etc) |
| */ |
| sysfs_bin_attr_init(&monza->bin); |
| monza->bin.attr.name = "monzax_data"; |
| monza->bin.attr.mode = S_IRUSR | S_IWUSR; |
| monza->bin.read = monza_bin_read; |
| monza->bin.write = monza_bin_write; |
| if (monza->num_addr == MONZAX_2K_ADDR_NUM) |
| monza->bin.size = MONZAX_2K_BYTE_LEN; |
| else if (monza->num_addr == MONZAX_8K_ADDR_NUM) |
| monza->bin.size = MONZAX_8K_BYTE_LEN; |
| else { |
| err = -ENODEV; |
| goto err_bin; |
| } |
| |
| err = sysfs_create_bin_file(&client->dev.kobj, &monza->bin); |
| if (err) |
| goto err_bin; |
| |
| i2c_set_clientdata(client, monza); |
| |
| monza->miscdev.minor = MISC_DYNAMIC_MINOR; |
| monza->miscdev.name = "monzax"; |
| monza->miscdev.fops = &monza_misc_fops; |
| |
| if (misc_register(&monza->miscdev)) { |
| dev_err(&client->dev, "misc_register failed\n"); |
| goto err_miscdev; |
| } |
| |
| dev_info(&client->dev, "%zu byte %s EEPROM, %u bytes/write\n", |
| monza->bin.size, client->name, monza->write_max); |
| |
| return 0; |
| |
| err_miscdev: |
| sysfs_remove_bin_file(&client->dev.kobj, &monza->bin); |
| err_bin: |
| kfree(monza->writebuf); |
| err_clients: |
| if (monza->client[1]) |
| i2c_unregister_device(monza->client[1]); |
| err_struct: |
| kfree(monza); |
| err_out: |
| dev_err(&client->dev, "probe error %d\n", err); |
| return err; |
| } |
| |
| static int monza_remove(struct i2c_client *client) |
| { |
| struct monza_data *monza; |
| |
| monza = i2c_get_clientdata(client); |
| misc_deregister(&monza->miscdev); |
| sysfs_remove_bin_file(&client->dev.kobj, &monza->bin); |
| kfree(monza->writebuf); |
| |
| if (monza->client[1]) |
| i2c_unregister_device(monza->client[1]); |
| |
| kfree(monza); |
| return 0; |
| } |
| |
| static struct i2c_driver monza_driver = { |
| .driver = { |
| .name = "monzax", |
| .owner = THIS_MODULE, |
| .acpi_match_table = ACPI_PTR(acpi_monza_ids), |
| }, |
| .probe = monza_probe, |
| .remove = monza_remove, |
| .id_table = i2c_monza_ids, |
| }; |
| |
| static int __init monza_init(void) |
| { |
| return i2c_add_driver(&monza_driver); |
| } |
| module_init(monza_init); |
| |
| static void __exit monza_exit(void) |
| { |
| i2c_del_driver(&monza_driver); |
| } |
| module_exit(monza_exit); |
| |
| MODULE_AUTHOR("Jiantao Zhou<jiantao.zhou@intel.com>"); |
| MODULE_DESCRIPTION("MONZA-X-2K RFID chip driver"); |
| MODULE_LICENSE("GPL v2"); |