/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 }, { "monzax", 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"); MODULE_DESCRIPTION("MONZA-X-2K RFID chip driver"); MODULE_LICENSE("GPL v2");