#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define CMD_LED_ON 1
#define CMD_LED_OFF 0
#define CMD_LED_GET_STATE 2
#define LEDS_NUM 4
#define S3C24XX_LEDS_PHY_BASE 0x56000050
#define GPFCON_OFFSET 0x0
#define GPFDAT_OFFSET 0x1
#define GPFUP_OFFSET 0x2
static int major = 0;
module_param(major, int, S_IRUGO|S_IWUGO);
MODULE_PARM_DESC(major,"s3c24xx leds major number"); //模塊參數設置
struct s3c24xx_leds_dev { //設備結構體
unsigned char led_state; //led_state [4-7], 1: on, 0: off
unsigned long *pBase;
//...
spinlock_t spin; //自旋鎖
};
struct s3c24xx_leds_dev *devs =NULL;
static int s3c24xx_leds_open (struct inode *inode, struct file *filp)
{
unsigned long * pReg = devs->pBase;
filp->private_data = (void *)devs;
//配置寄存器
//GPFCON output.
writel( (readl(pReg +GPFCON_OFFSET) &~0xff00) |0x5500, pReg +GPFCON_OFFSET);
//GPFUP disable pullup.
writel( (readl(pReg +GPFUP_OFFSET) &~0xff00) |0x5500, pReg +GPFUP_OFFSET);
return 0;
}
static int s3c24xx_leds_close (struct inode *inode, struct file *filp)
{
filp->private_data = NULL;
return 0;
}
static ssize_t s3c24xx_leds_read (struct file * filp, char __user *buf, size_t len, loff_t *loff)
{
struct s3c24xx_leds_dev *pdev =(struct s3c24xx_leds_dev *)filp->private_data;
unsigned char kbuff;
spin_lock(&pdev->spin); //上鎖
kbuff = pdev->led_state >>4;
spin_unlock(&pdev->spin); //解鎖
if (copy_to_user((void __user *)buf, &kbuff, 1) )
{
return -EFAULT;
}
return 0;
}
static ssize_t s3c24xx_leds_write (struct file * filp, char __user *buf, size_t len, loff_t *loff)
{
struct s3c24xx_leds_dev *pdev =(struct s3c24xx_leds_dev *)filp->private_data;
unsigned char kbuff;
if (copy_from_user(&kbuff, (void __user *)buf, 1))
{
return -EFAULT;
}
kbuff = kbuff<<4;
//真正的LED操作
spin_lock(&pdev->spin); //上鎖
if ( pdev->led_state != kbuff ) {
//...
int i;
for (i = 0; i< LEDS_NUM;i++) {
if( (pdev->led_state & (1<< (4+i))) ^ (kbuff & (1<<( 4+i))) ) {
if (pdev->led_state & (1<< (4+i)) ) { // orignal led state is on,now set to off
writel( (readl(pdev->pBase +GPFDAT_OFFSET) &~0xf0) | (1<< ( 4+i )), pdev->pBase+GPFDAT_OFFSET);
} else { // orignal led state is off,now set to on
writel( ((readl(pdev->pBase +GPFDAT_OFFSET) &~0xf0)) | ~(1<< ( 4+i)) & 0xf0, pdev->pBase+GPFDAT_OFFSET);
}
}
}
}
pdev->led_state = kbuff;
spin_unlock(&pdev->spin); //解鎖
return 0;
}
static int s3c24xx_leds_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
struct s3c24xx_leds_dev *pdev =(struct s3c24xx_leds_dev *)filp->private_data;
unsigned char state;
//參數正確性驗證
if (arg <1 || arg >4)
return -EINVAL;
//權限管理
if (!capable (CAP_SYS_ADMIN)) {
return -EPERM;
}
switch (cmd) {
case CMD_LED_ON:
//...
spin_lock(&pdev->spin);
// writel( readl(pdev->pBase +GPFDAT_OFFSET) &~0xf0 |0xe0, pdev->pBase+GPFDAT_OFFSET);
writel( readl(pdev->pBase +GPFDAT_OFFSET) &~0xf0 | ~(1<< ( 4+arg -1)) & 0xf0, pdev->pBase+GPFDAT_OFFSET);
pdev->led_state |= 1<< ( 4+arg -1);
spin_unlock(&pdev->spin);
break;
case CMD_LED_OFF:
spin_lock(&pdev->spin);
//writel( readl(pdev->pBase +GPFDAT_OFFSET) &~0xf0 |0xf0, pdev->pBase+GPFDAT_OFFSET);
writel( readl(pdev->pBase +GPFDAT_OFFSET) | 1<< ( 4+arg -1), pdev->pBase+GPFDAT_OFFSET);
pdev->led_state &= ~(1<< ( 4+arg -1));
spin_unlock(&pdev->spin);
break;
case CMD_LED_GET_STATE:
spin_lock(&pdev->spin);
state = pdev->led_state >>4;
spin_unlock(&pdev->spin);
printk("driver: led_sate:%0x", state& 0x0f );
return copy_to_user((void *) arg, &state, 1) ? -EFAULT : 0;
break;
default:
printk("Unknown command\n");
break;
}
return 0;
}
static struct file_operations s3c24xx_leds_ops = {
.owner = THIS_MODULE,
.open = s3c24xx_leds_open,
.release = s3c24xx_leds_close,
.read = s3c24xx_leds_read,
.write = s3c24xx_leds_write,
.ioctl = s3c24xx_leds_ioctl,
//...
};
static int __init s3c24xx_leds_init(void)
{
dev_t dev_no = MKDEV(major, 0);
int itmp = -1, ret = -1;
unsigned long *pmem = NULL;
devs = kmalloc(sizeof(struct s3c24xx_leds_dev), GFP_KERNEL);
if (!devs) {
ret = -ENOMEM;
goto out;
}
itmp = register_chrdev(dev_no, "leds", &s3c24xx_leds_ops);
if(itmp < 0) {
ret = -ENODEV;
goto out1;
}
if( itmp > 0 )
major = itmp;
printk("major = %d\n", major);
//地址映射
pmem = ioremap(S3C24XX_LEDS_PHY_BASE, 12);
if (!pmem) {
ret = -ENOMEM;
goto out2;
}
devs->pBase = pmem;
spin_lock_init(&devs->lock);
return 0;
out2:
unregister_chrdev(dev_no, "leds");
out1:
kfree(devs);
out:
return ret;
}
static void __exit s3c24xx_leds_exit(void)
{
dev_t dev_no = MKDEV(major, 0);
iounmap(devs->pBase);
unregister_chrdev(dev_no, "leds");
kfree(devs);
devs = NULL;
}
module_init(s3c24xx_leds_init);
module_exit(s3c24xx_leds_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("StephenYee([email protected])");