301 lines
7.3 KiB
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 = ℞
|
|
|
|
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 = ℞
|
|
|
|
x[2].tx_buf = &att7022->cmd_info->data[2];
|
|
x[3].rx_buf = ℞
|
|
|
|
x[4].tx_buf = &att7022->cmd_info->data[1];
|
|
x[5].rx_buf = ℞
|
|
|
|
x[6].tx_buf = &att7022->cmd_info->data[0];
|
|
x[7].rx_buf = ℞
|
|
/* 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");
|
|
|