524 lines
12 KiB
C
524 lines
12 KiB
C
/*
|
|
* 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");
|