本文主要探讨x210驱动的平台设备类型(platform)以及misc设备。
驱动模型
设备驱动模型:总线(bus type)、设备(device)和驱动(driver)
总线:虚拟总线用于挂接驱动驱动和设备
总线、设备、驱动关系:/sys/bus下的子目录对应不同总线,总线目录有设备、驱动目录和、总线属性文件
总线
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct bus_type_private *p;
};
总线属性
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
设备属性
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
驱动属性
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *driver, char *buf);
ssize_t (*store)(struct device_driver *driver, const char *buf,
size_t count);
};
公共属性结构体(show(read)和store(write)方法)
struct attribute {
const char *name;
struct module *owner;
mode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
总线示例
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
name总线名称,struct bus_attribute,struct device_attribute,struct driver_attribut为总线,设备,驱动属性和方法
match为设备和驱动匹配函数,uevent内核向用户通知事件,probe探测函数,remove移除函数,shutdown关闭函数,suspend挂起,resume唤醒,struct dev_pm_ops *pm;电源管理,struct bus_type_private *p;私有数据
struct bus_type_private {
struct kset subsys;
struct kset *drivers_kset;
struct kset *devices_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
};
struct kset subsys;总线子系统,kobj为子系统顶层目录
struct kset *drivers_kset;struct kset *devices_kset;挂载到该总线上的驱动和设备集合
struct klist klist_devices;struct klist klist_drivers;设备和驱动链表
unsigned int drivers_autoprobe:1;注册时自动探测设备
struct bus_type *bus;回指针
设备
struct device
{
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata archdata;
#ifdef CONFIG_OF
struct device_node *of_node;
#endif
dev_t devt; /* dev_t, creates the sysfs "dev" */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
}
device结构体用于描述设备
struct device *parent;父设备
struct device_private *p;私有数据
struct device_private {
struct klist klist_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
void *driver_data;
struct device *device;
};
const char *init_name;设备名
struct bus_type *bus;指向总线
struct device_type *type;设备处理函数
struct device_type {
const char *name;
const struct attribute_group **groups;
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, mode_t *mode);
void (*release)(struct device *dev);
const struct dev_pm_ops *pm;
};
struct device_driver *driver;设备驱动
struct kobject kobj;内嵌kobject
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
void (*release)(struct device *dev);释放设备回调函数
dev_t devt;设备号
struct class *class;设备类类指针
void *platform_data;平台设备数据(gpio、中断号、设备名称等)
设备注册和注销
int device_register(struct device *dev);
void device_unregister(struct device *dev);
宏DEVICE_ATTR定义attribute结构
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
创建和删除device目录下的属性文件
int device_create_file(struct device *dev,const struct device_attribute *attr);
void device_remove_file(struct device *dev,const struct device_attribute *attr);
驱动
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
#if defined(CONFIG_OF)
const struct of_device_id *of_match_table;
#endif
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
}
const char *name;驱动名字与设备名字相同总线match匹配函数才可对应找到
struct bus_type *bus;指向驱动总线
const char *mod_name;驱动模块名字
probe驱动探测方法(装载使用),remove删除驱动,shutdown关闭驱动方法,suspend挂起方法,resume唤醒方法
struct driver_private *p;设备驱动私有数据
struct driver_private {
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};
const struct attribute_group **groups;驱动属性组
struct attribute_group {
const char *name;
mode_t (*is_visible)(struct kobject *,
struct attribute *, int);
struct attribute **attrs;
};
驱动程序注册和注销
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);
驱动属性文件删除和创建
int driver_create_file(struct device_driver *drv, const struct driver_attribute *attr);
void driver_remove_file(struct device_driver *drv,const struct driver_attribute *attr);
platform(drivers/base/platform.c)
平台设备:wdt、i2c、i2s、rtc,adc等
CPU与外部通信:地址总线、专用接口
平台总线为地址总线式连接设备(soc内部外设)
平台设备(struct platform_device)
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
name设备名,struct device dev;设备结构体
const struct platform_device_id *id_entry;设备ID表
struct pdev_archdata archdata;预留
struct resource * resource;资源数组
u32 num_resources;设备使用资源数量
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
static struct resource s3c_wdt_resource[] = {
[0] = {
.start = S3C_PA_WDT,
.end = S3C_PA_WDT + SZ_1M - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_WDT,
.end = IRQ_WDT,
.flags = IORESOURCE_IRQ,
}
};
start资源起始地址(WTCON),资源结束地址,name资源名,flags资源类型(IORESOURCE_MEM内存资源)
start资源起始地址(IRQ_WDT中断开始信号),资源结束地址(IRQ_WDT中断结束信号),flags资源类型(IORESOURCE_IRQ中断资源)
flags标志:I/O端口(IORESOURCE_IO)、内存资源(IORESOURCE_MEM)、中断号(IORESOURCE_IRQ)、DMA资源(IORESOURCE_DMA)
struct resource *parent, *sibling, *child;构建资源设备树
平台设备函数
int platform_device_add(struct platform_device *pdev);
void platform_device_del(struct platform_device *pdev);
int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
平台设备驱动
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
struct device_driver driver;设备驱动结构体
const struct platform_device_id *id_table;设备ID表
probe驱动探测方法(装载使用),remove删除驱动,shutdown关闭驱动方法,suspend挂起方法,resume唤醒方法
平台驱动注册(probe)和注销(remove)
int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);
misc
杂项主设备号通常为10
设备类文件:/sys/class/misc
混杂设备
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
minor次设备号,name设备名,fops设备操作函数,this_device当前设备指针
混杂设备的注册和注销
int misc_register(struct miscdevice * misc);
int misc_deregister(struct miscdevice *misc);
210蜂鸣器(x210-buzzer.c)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <plat/regs-timer.h>
#include <mach/regs-irq.h>
#include <asm/mach/time.h>
#include <linux/clk.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <plat/gpio-cfg.h>
//定义设备名
#define DEVICE_NAME "buzzer"
//定义cmd
#define PWM_IOCTL_SET_FREQ 1
#define PWM_IOCTL_STOP 0
//定义信号量
static struct semaphore lock;
//初始化buzzer,设置频率
// TCFG0在Uboot中设置,这里不再重复设置
// Timer0输入频率Finput=pclk/(prescaler1+1)/MUX1=66M/16/16
// TCFG0 = tcnt = (pclk/16/16)/freq;
// PWM0输出频率Foutput =Finput/TCFG0= freq
static void PWM_Set_Freq( unsigned long freq )
{
unsigned long tcon;
unsigned long tcnt;
unsigned long tcfg1;
struct clk *clk_p;
unsigned long pclk;
//unsigned tmp;
//设置GPD0_2为PWM输出
s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(2));
tcon = __raw_readl(S3C2410_TCON);
tcfg1 = __raw_readl(S3C2410_TCFG1);
//mux = 1/16
tcfg1 &= ~(0xf<<8);
tcfg1 |= (0x4<<8);
__raw_writel(tcfg1, S3C2410_TCFG1);
clk_p = clk_get(NULL, "pclk");
pclk = clk_get_rate(clk_p);
tcnt = (pclk/16/16)/freq;
__raw_writel(tcnt, S3C2410_TCNTB(2));
__raw_writel(tcnt/2, S3C2410_TCMPB(2));//占空比为50%
tcon &= ~(0xf<<12);
tcon |= (0xb<<12); //disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0
__raw_writel(tcon, S3C2410_TCON);
tcon &= ~(2<<12); //clear manual update bit
__raw_writel(tcon, S3C2410_TCON);
}
//停止buzzer(改为输入模式)
void PWM_Stop( void )
{
//将GPD0_2设置为input
s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(0));
}
//对应open,开锁
static int x210_pwm_open(struct inode *inode, struct file *file)
{
if (!down_trylock(&lock))
return 0;
else
return -EBUSY;
}
//对应close,上锁
static int x210_pwm_close(struct inode *inode, struct file *file)
{
up(&lock);
return 0;
}
// PWM:GPF14->PWM0(接收cmd,设置频率,关闭buzzer)
static int x210_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case PWM_IOCTL_SET_FREQ:
printk("PWM_IOCTL_SET_FREQ:\r\n");
if (arg == 0)
return -EINVAL;
PWM_Set_Freq(arg);
break;
case PWM_IOCTL_STOP:
default:
printk("PWM_IOCTL_STOP:\r\n");
PWM_Stop();
break;
}
return 0;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = x210_pwm_open,
.release = x210_pwm_close,
.ioctl = x210_pwm_ioctl,
};
//定义msic设备
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
//驱动装载
static int __init dev_init(void)
{
int ret;
init_MUTEX(&lock);
ret = misc_register(&misc);
/* GPD0_2 (PWMTOUT2) */
ret = gpio_request(S5PV210_GPD0(2), "GPD0");
if(ret)
printk("buzzer-x210: request gpio GPD0(2) fail");
s3c_gpio_setpull(S5PV210_GPD0(2), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(1));
gpio_set_value(S5PV210_GPD0(2), 0);
printk ("x210 "DEVICE_NAME" initialized\n");
return ret;
}
//驱动卸载
static void __exit dev_exit(void)
{
misc_deregister(&misc);
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.9tripod.com");
MODULE_DESCRIPTION("x210 PWM Driver");
buzzer.c
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEVNAME "/dev/buzzer"
#define PWM_IOCTL_SET_FREQ 1
#define PWM_IOCTL_STOP 0
int main(void)
{
int fd = -1;
fd = open(DEVNAME, O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
ioctl(fd, PWM_IOCTL_SET_FREQ, 10000);
sleep(3);
ioctl(fd, PWM_IOCTL_STOP);
close(fd);
}
demo:
使用平台总线方式驱动led(模仿leds-s3c24xx.c)
Makefile
KERN_DIR = /root/kernel
obj-m += x210-led.o
all:
make -C $(KERN_DIR) M=`pwd` modules
cp:
cp *.ko /root/rootfs/driver
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
x210-led.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <mach/leds_x210-gpio.h>
#define LED_OFF 1
#define LED_ON 0
struct s5pv210_gpio_led
{
struct led_classdev cdev;
struct s5pv210_led_platdata *pdata;
};
static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
return platform_get_drvdata(dev);
}
static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}
static void s5pv210_led_set(struct led_classdev *led_cdev,enum led_brightness value)
{
struct s5pv210_gpio_led *p = to_gpio(led_cdev);
if(value == LED_OFF)
{
gpio_set_value(p->pdata->gpio,LED_OFF);
}
else
{
gpio_set_value(p->pdata->gpio,LED_ON);
}
}
int s5pv210_led_probe(struct platform_device *dev)
{
int ret = -1;
struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
struct s5pv210_gpio_led *led;
led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
if (led == NULL) {
dev_err(&dev->dev, "No memory for device\n");
return -ENOMEM;
}
platform_set_drvdata(dev, led);
ret = gpio_request(pdata->gpio, pdata->name);
if (ret != 0)
{
printk(KERN_ERR "gpio_request failed %d\n",ret);
return ret;
}
else
{
gpio_direction_output(pdata->gpio,GPIOF_OUT_INIT_HIGH);
}
led->cdev.name = pdata->name;
led->cdev.brightness = 0;
led->cdev.brightness_set = s5pv210_led_set;
led->pdata = pdata;
ret = led_classdev_register(&dev->dev, &led->cdev);
if (ret < 0)
{
printk(KERN_ERR "led classdev register failed\n");
kfree(led);
return ret;
}
return 0;
}
static int s5pv210_led_remove(struct platform_device *dev)
{
struct s5pv210_gpio_led *p = pdev_to_gpio(dev);
led_classdev_unregister(&p->cdev);
gpio_free(p->pdata->gpio);
kfree(p);
return 0;
}
struct platform_driver s5pv210_led_driver[] ={
[0] = {
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver = {
.name = "led1",
.owner = THIS_MODULE,
}},
[1] = {
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver = {
.name = "led2",
.owner = THIS_MODULE,
}},
[2] = {
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver = {
.name = "led3",
.owner = THIS_MODULE,
}},
};
static int __init led_dev_init(void)
{
platform_driver_register(&s5pv210_led_driver[0]);
platform_driver_register(&s5pv210_led_driver[1]);
platform_driver_register(&s5pv210_led_driver[2]);
return 0;
}
static void __exit led_dev_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver[0]);
platform_driver_unregister(&s5pv210_led_driver[1]);
platform_driver_unregister(&s5pv210_led_driver[2]);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cxb");
MODULE_DESCRIPTION("led gpio module");
MODULE_ALIAS("led");
操作流程
vim arch/arm/mach-s5cpv210/include/mach/leds_x210-gpio.h
添加设备特殊数据结构体(gpio、中断号、设备名称等)
#ifndef __ASM_ARCH_LEDSGPIO_H
#define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"
#define S5PV210_LEDF_ACTLOW (1<<0) /* LED is on when GPIO low */
#define S5PV210_LEDF_TRISTATE (1<<1) /* tristate to turn off */
struct s5pv210_led_platdata {
unsigned int gpio;
unsigned int flags;
char *name;
char *def_trigger;
};
#endif /* __ASM_ARCH_LEDSGPIO_H */
vim arch/arm/mach-s5pv210/mach-x210.c
添加设备特殊数据结构体
#include <mach/leds_x210-gpio.h>
定义如下平台总线设备到smdkc110_devices[]前
static struct s5pv210_led_platdata x210_led1_pdata = {
.name = "led1",
.gpio = S5PV210_GPJ0(3),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "",
};
static struct platform_device x210_led1 = {
.name = "led1",
.id = 0,
.dev = {
.platform_data = &x210_led1_pdata,
},
};
static struct s5pv210_led_platdata x210_led2_pdata = {
.name = "led2",
.gpio = S5PV210_GPJ0(4),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "",
};
static struct platform_device x210_led2 = {
.name = "led2",
.id = 1,
.dev = {
.platform_data = &x210_led2_pdata,
},
};
static struct s5pv210_led_platdata x210_led3_pdata = {
.name = "led3",
.gpio = S5PV210_GPJ0(5),
.flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,
.def_trigger = "",
};
static struct platform_device x210_led3 = {
.name = "led3",
.id = 2,
.dev = {
.platform_data = &x210_led3_pdata,
},
};
添加如下平台总线设备到smdkc110_devices[]
&x210_led1
&x210_led2
&x210_led3
make -j8 && cp arch/arm/boot/zImage ~/tftp -f
make cp
结果示例: