ckfwq/linux-3.0.4/drivers/char/att7022.c

301 lines
7.3 KiB
C

/*
* att7022 power test spi serial driver
*
* Copyright (C) 2013 BLX IC Design Corp.,Ltd.
* Author: Davied, apple_guet@126.com
*
*/
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/spi/spi.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/types.h>
#ifdef CONFIG_SPI_POWER_ATT7022_DEBUG
#define DBG(msg...) do { \
printk(KERN_INFO msg); \
} while (0)
#else
#define DBG(msg...) do { } while(0)
#endif
#define ATT7022_BIT_LOCK_OPEN 0x00
/* 16 spi flash should be enough for everyone */
#define ATT7022_MAX_MINOR 16
#define ATT7022_POWER_NAME "att7022-power-test"
struct cmd_info_s {
u8 addr;
u8 data[3];
};
/* ioctl command */
#define ATT7022_IOC_MAGIC 'a'
#define ATT7022_IOC_MAXNR 2
#define ATT7022_READ_DATA _IOWR(ATT7022_IOC_MAGIC, 0, struct cmd_info_s)
#define ATT7022_CHECK_DATA _IOWR(ATT7022_IOC_MAGIC, 1, struct cmd_info_s)
struct att7022_dev {
char name[20];
unsigned long bit_lock;
dev_t devt;
struct cdev cdev;
struct device dev;
struct class *class;
struct mutex mlock;
struct spi_device *spi;
struct cmd_info_s *cmd_info;
struct list_head device_entry;
};
static int att7022_open(struct inode *inode, struct file *file)
{
struct att7022_dev *att7022 = container_of(inode->i_cdev, struct att7022_dev, cdev);
if (test_and_set_bit(ATT7022_BIT_LOCK_OPEN, &att7022->bit_lock)) {
DBG("!!!!att7022 open err, busy!\n");
return -EBUSY;
}
file->private_data = att7022;
return 0;
}
static int at7022_read_data(struct att7022_dev *att7022)
{
u8 i = 0, rx = 0, dummy_value = 0xff;
struct spi_transfer x[8];
struct spi_message message;
memset(x, 0, sizeof x);
spi_message_init(&message);
for (i = 0; i < 8; i++) {
x[i].len = 1;
spi_message_add_tail(&x[i], &message);
}
att7022->cmd_info->addr &= 0x7F;
x[0].tx_buf = &att7022->cmd_info->addr; //read cmd
x[1].rx_buf = &rx;
x[2].tx_buf = &dummy_value;
x[3].rx_buf = &att7022->cmd_info->data[2];
x[4].tx_buf = &dummy_value;
x[5].rx_buf = &att7022->cmd_info->data[1];
x[6].tx_buf = &dummy_value;
x[7].rx_buf = &att7022->cmd_info->data[0];
/* do the i/o */
return spi_sync(att7022->spi, &message);
}
static int at7022_check_data(struct att7022_dev *att7022)
{
u8 i = 0, rx = 0;
struct spi_transfer x[8];
struct spi_message message;
memset(x, 0, sizeof x);
spi_message_init(&message);
for (i = 0; i < 8; i++) {
x[i].len = 1;
spi_message_add_tail(&x[i], &message);
}
switch (att7022->cmd_info->addr) {
case 0x80:
case 0xC3:
case 0xD3:
att7022->cmd_info->addr |= 0xC0;
x[0].tx_buf = &att7022->cmd_info->addr; //special cmd
break;
default:
att7022->cmd_info->addr &= 0xBF;
att7022->cmd_info->addr |= 0x80;
x[0].tx_buf = &att7022->cmd_info->addr; //check cmd
break;
}
x[1].rx_buf = &rx;
x[2].tx_buf = &att7022->cmd_info->data[2];
x[3].rx_buf = &rx;
x[4].tx_buf = &att7022->cmd_info->data[1];
x[5].rx_buf = &rx;
x[6].tx_buf = &att7022->cmd_info->data[0];
x[7].rx_buf = &rx;
/* do the i/o */
return spi_sync(att7022->spi, &message);
}
static long att7022_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
void __user *argp = (void __user *)arg;
struct att7022_dev *att7022= file->private_data;
DBG("@@@@att7022 ioctl start.\n");
ret = mutex_lock_interruptible(&att7022->mlock);
if (ret) {
DBG("!!!!mutex lock error!\n");
return ret;
}
att7022->cmd_info = kzalloc(sizeof(struct cmd_info_s), GFP_KERNEL);
if (!att7022->cmd_info) {
DBG("!!!!kzalloc error!\n");
ret = -ENOMEM;
goto exit;
}
if (copy_from_user(att7022->cmd_info, argp, sizeof(struct cmd_info_s))) {
DBG("!!!!copy_from_user() error!\n");
ret = -EFAULT;
goto exit;
}
if ((_IOC_TYPE(cmd) != ATT7022_IOC_MAGIC) || (_IOC_NR(cmd) > ATT7022_IOC_MAXNR)) {
DBG("!!!!ioc type or ioc nr error!\n");
ret = -ENOTTY;
goto exit;
}
switch(cmd) {
case ATT7022_READ_DATA:
DBG("att7022 read data cmd\n");
ret = at7022_read_data(att7022);
break;
case ATT7022_CHECK_DATA:
DBG("att7022 check data cmd\n");
ret = at7022_check_data(att7022);
break;
default:
DBG("!!!!cmd error!\n");
ret = -ENOTTY;
break;
}
if (ret == 0) {
if (copy_to_user(argp, att7022->cmd_info, sizeof(struct cmd_info_s)))
ret = -EFAULT;
}
exit:
mutex_unlock(&att7022->mlock);
kfree(att7022->cmd_info);
if (ret != 0)
DBG("!!!!att7022 ioctl error!\n");
else {
DBG("att7022 ioctl success.\n");
}
return ret;
}
static int att7022_release(struct inode *inode, struct file *file)
{
int ret = 0;
struct att7022_dev *att7022= file->private_data;
clear_bit_unlock(ATT7022_BIT_LOCK_OPEN, &att7022->bit_lock);
return ret;
}
static const struct file_operations att7022_fops = {
.owner = THIS_MODULE,
.open = att7022_open,
.unlocked_ioctl = att7022_ioctl,
.release = att7022_release,
};
static int __devinit att7022_probe(struct spi_device *spi)
{
int ret = 0;
struct att7022_dev *att7022;
DBG("############\n");
DBG("att7022 power test probe start.\n");
att7022 = kzalloc(sizeof *att7022, GFP_KERNEL);
if (!att7022) {
DBG("!!!!kzalloc error!\n");
return -ENOMEM;
}
att7022->spi = spi;
mutex_init(&att7022->mlock);
strlcpy(att7022->name, ATT7022_POWER_NAME, sizeof(att7022->name));
ret = alloc_chrdev_region(&att7022->devt, 0, ATT7022_MAX_MINOR, "att7022");
if (ret < 0) {
DBG("!!!!%s: failed to allocate char dev region!\n", __FILE__);
goto err_kzall;
}
att7022->dev.devt = MKDEV(MAJOR(att7022->devt), 1);
cdev_init(&att7022->cdev, &att7022_fops);
att7022->cdev.owner = THIS_MODULE;
ret = cdev_add(&att7022->cdev, att7022->devt, 1);
if (ret) {
DBG("!!!!cdev add error!\n");
goto err_alloc;
}
att7022->class = class_create(THIS_MODULE, "att7022-spi");
if (IS_ERR(att7022->class)) {
DBG("!!!!failed in create att7022 power test class!\n");
goto err_alloc;
}
device_create(att7022->class, NULL, att7022->devt, NULL, "att7022");
dev_set_drvdata(&spi->dev, att7022);
DBG("att7022 power test probe success.\n");
DBG("############\n");
return 0;
err_alloc:
unregister_chrdev_region(att7022->devt, ATT7022_MAX_MINOR);
err_kzall:
kfree(att7022);
printk(KERN_ERR "!!!!!!att7022 power test probe error!\n");
return ret;
}
static int __devexit att7022_remove(struct spi_device *spi)
{
struct att7022_dev *att7022 = dev_get_drvdata(&spi->dev);
cdev_del(&att7022->cdev);
unregister_chrdev_region(att7022->devt, ATT7022_MAX_MINOR);
device_destroy(att7022->class, att7022->devt);
class_destroy(att7022->class);
kfree(att7022);
return 0;
}
static struct spi_driver att7022_driver = {
.driver = {
.name = "spi-att7022",
.owner = THIS_MODULE,
},
//.id_table = att7022_ids,
.probe = att7022_probe,
.remove = __devexit_p(att7022_remove),
};
static int __init att7022_init(void)
{
return spi_register_driver(&att7022_driver);
}
static void __exit att7022_exit(void)
{
spi_unregister_driver(&att7022_driver);
}
module_init(att7022_init);
module_exit(att7022_exit);
MODULE_DESCRIPTION("att7022 spi power test");
MODULE_AUTHOR("Davied<apple_guet@126.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi driver:att7022");