1. 例-字符设备驱动(开关灯)

字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少要实现open,close,read和write系统调用。大多数字符设备是一个个只能顺序访问的数据通道。
1.管理设备号

dev_t dev = MKDEV(主设备号,次设备号)    -->组合设备号
主设备号 = MAJOR(dev_t dev)              -->提取主设备号
次设备号=MINOR(dev_t dev)                -->提取次设备号

2.分配设备号

#include <linux/fs.h>
int register_chrdev_region(dev_t from, unsigned count, const char *name)   -->静态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                                       const char *name);   -->动态分配
                                                                                          返回值:成功,0;失败,负数

参数说明:
count: 该类设备的设备个数
name: 设备名
baseminer:次设备号
3.注销设备号

#include <linux/fs.h>
void unregister_chrdev_region(dev_t from, unsigned count);

4.分配cdev

#include <linux/cdev.h>
方式一:静态分配
        struct cdev mdev;
方式二:动态分配
       struct cdev *pdev = cdev_alloc();

参数说明:
struct cdev {
struct kobject kobj; /* 内嵌的 kobject 对象 */
struct module *owner; /所属模块/
struct file_operations *ops; /文件操作结构体/
struct list_head list;
dev_t dev; /设备号/
unsigned int count;
};
如:

#include <linux/fs.h>
struct file_operations dev_fops = {       //这里只是简单的举例
        .llseek = NULL,
        .read = dev_read,
        .write = dev_write,
        .ioctl = dev_ioctl,
        .open = dev_open,
        .release = dev_release,
};

5.初始化cdev

#include <linux/cdev.h>
void  cdev_init(struct cdev *, struct file_operations *);

参数说明:
cdev: 待初始化的cdev结构
fops: 设备对应的操作函数集

6.向内核注册字符设备

#include <linux/cdev.h>
int  cdev_add(struct cdev *p, dev_t dev, unsigned count)

参数说明:
p: 待添加到内核的字符设备结构
dev: 设备号
count: 该类设备的设备个数
7.设备操作原型

int (*open) (struct inode *inode, struct file *file)
打开设备,响应open系统

int (*release) (struct inode *inode, struct file *file);
关闭设备,响应close系统调用

 loff_t (*llseek) (struct file *file, loff_t offset, int whence)
重定位读写指针,响应lseek系统调用

 ssize_t (*read) (struct file *file, char __user *buf, size_t count, loff_t *ppos)
从设备读取数据,响应read系统调用

 ssize_t (*write)(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
向设备写入数据,响应write系统调用

8.创建设备节点

//创建设备节点
	cls = class_create(THIS_MODULE,"irq_driver");
	class_dev = device_create(cls, NULL ,MKDEV(major,0),NULL,"buttons");
                            //buttons为/dev目录下的设备文件名

9.内核空间和用户空间的内存拷贝

#include <linux/uaccess.h>
int  copy_from_user(void *to, const void __user *from, int n)
int  copy_to_user(void __user *to, const void *from, int n)

10.注销驱动

void  cdev_del(struct cdev *)

11.设备控制
–>应用函数

int ioctl(int fd,unsigned long cmd,...)  

参数说明

fd要控制的设备文件描述符
cmd发送给设备的控制命令
Size参数长度
第3个参数是可选的参数,存在与否是依赖于控 制命令(第 2 个参数 )

cmd组成
Type(类型/幻数): 表明这是属于哪个设备的命令。
Number(序号),用来区分同一设备的不同命令
Direction:参数传送的方向,可能的值是 _IOC_NONE(没有
数据传输), _IOC_READ, _IOC_WRITE(向设备写入参数)

–>驱动函数
2.6.36 之前的内核

long (*ioctl) (struct inode* node ,struct file* filp, unsigned int cmd,unsigned long arg)

2.6.36之后的内核

long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)

12.定义命令

#include <asm-generic/ioctl.h>
_IO(type,nr):不带参数的命令
_IOR(type,nr,datatype):从设备中读参数的命令
_IOW(type,nr,datatype):向设备写入参数的命令

参数说明:
type(类型/幻数): 表明这是属于哪个设备的命令。
nr序号 ,用来区分同一设备的不同命令
datatype:数据类型
如:

#define MEM_MAGIC ‘m’ //定义幻数
#define MEM_SET _IOW(MEM_MAGIC, 0, int)

13.一个简单的耳灯控制例程
–>dts配置

&soc {
	earlight {
		compatible = "allwinner,earlight";
		gpio_en = <&r_pio PL 11 1 0 1 0>;
		status = "okay";
	};
};

–>驱动代码

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/pm_runtime.h>


//#define PER (HZ/1000)
#define LED_DEVICE_NAME "earlight"

static int led_gpio = -1;

static dev_t gpio_dev_t;
static struct cdev *char_dev;
static struct class *cls;
static struct device *dev;
static int light = 0;

static const struct of_device_id earlight_of_match[] = {
        {.compatible = "allwinner,earlight",},
        { /* sentinel */ }
};


//static int earlight_control(u8 onoff) {
//	if(led_gpio) {
//		if(onoff) {
//			return gpio_set_value(led_gpio,1);
//		} else {
//			return gpio_set_value(led_gpio,0);
//		}
//	}
//	
//	return -1;
//}

static int led_open (struct inode *inode, struct file *filp){
	return 0;
}

 ssize_t led_read (struct file *file, char __user *buf,size_t count, loff_t *ppos) {
	int ret = -1;
	ret = copy_to_user(buf, &light, sizeof(light));
	if(ret < 0) printk(KERN_ERR"%s:error read !!!\n",__func__);
	
	return sizeof(light);
 }

ssize_t led_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
	char vbuff[5] = {'\0','\0','\0','\0','\0'};
	int value = 0;
	int err = -1;
	
	if(count > 4) {
		printk(KERN_ERR"%s:too large to write !!!\n",__func__);
		return count;
	}
	
	err = copy_from_user(vbuff, buf, count);
	if(err < 0){
		printk(KERN_ERR"%s:error write !!!\n",__func__);
		return count;
	}
	
	if(count == 1){
		value = vbuff[0];
		printk(KERN_ERR"%s:value= %d\n",__func__,value);
		if(value>=0 && value<=10){
			light = (uint8_t)value;
		} else if(value>=0x30 && value<=(10+0x30)){
			light = (uint8_t)value - 0x30;
		}
		
		gpio_set_value(led_gpio,!!light);
		
		return count;
	}
	
	//printk(KERN_ERR"%s: vbuff = %s\n",__func__,vbuff);
	err = sscanf(vbuff,"%d",&value);
	if(err < 0) {
		printk(KERN_ERR"%s: value is invalid!!!\n",__func__);
		return count;
	}
	printk(KERN_ERR"%s:value= %d\n",__func__,value);
	if(value>=0 && value<=10){
		light = (uint8_t)value;
	}
	
	gpio_set_value(led_gpio,!!light);
	
	return count;
}


static int led_close (struct inode *inode, struct file *filp){
	return 0;	
}


static struct file_operations fops = {
	.owner          = THIS_MODULE,
	.open           = led_open,
	.read			= led_read,
	.write			= led_write,
	.release        = led_close,
};


static int earlight_probe(struct platform_device *pdev) {
	
	struct device_node *np = pdev->dev.of_node;
	int ret = -1;
	
	printk(KERN_ERR"------%s------\n",__func__);
	
	if (!np) {
        dev_err(&pdev->dev, "laser: Device Tree np missing\n");
        return -EINVAL;
    }
    
    led_gpio = of_get_named_gpio_flags(np, "gpio_en", 0, NULL);
	if (!gpio_is_valid(led_gpio)) {
		pr_err("%s: led_gpio is invalid. \n", __func__);
		return -1;
	}
	
	ret = gpio_request(led_gpio, "ear-light");
	if(ret < 0){
		pr_err("%s: can't request led_gpio. \n", __func__);
		return -1;
	}
    gpio_direction_output(led_gpio,1);
	gpio_set_value(led_gpio,1);
	light = 1;
    
	/* 申请设备号 */
	ret = alloc_chrdev_region(&gpio_dev_t,0,1,LED_DEVICE_NAME);
	if(ret < 0){
		printk(KERN_ERR"dev_t request error!\n");
		goto chrdev_alloc_error;
	}
	
	/* 分配cdev */
	char_dev = cdev_alloc();
	if (NULL == char_dev || IS_ERR(char_dev)) {
		printk(KERN_ERR"char_dev fail!\n");
		goto cdev_alloc_error;
	}
	
	/* 初始化cdev */
	cdev_init(char_dev, &fops);
	
	/* 注册cdev */
	ret = cdev_add(char_dev, gpio_dev_t, 1);
	if(ret < 0){
		printk(KERN_ERR"cdev add error!\n");
		goto cdev_add_error;
	}
	
	/* 创建设备节点 */
	cls = class_create(THIS_MODULE, LED_DEVICE_NAME);
	dev = device_create(cls, NULL, gpio_dev_t, NULL, LED_DEVICE_NAME);
	if (NULL == dev || IS_ERR(dev)) {
		printk(KERN_ERR"device_create fail!\n");
		goto device_creat_error;
	}
	
	
	return 0;

/* 异常处理 */
device_creat_error:
	class_destroy(cls);
	cdev_del(char_dev);
	
cdev_add_error:
//	kfree(char_dev);
	
cdev_alloc_error:
	unregister_chrdev_region(gpio_dev_t,1);
	
chrdev_alloc_error:
	gpio_set_value(led_gpio, 0);
	gpio_free(led_gpio);

	return -1;

}

static int earlight_remove(struct platform_device *pdev) {
	device_del(dev);
	class_destroy(cls);

	cdev_del(char_dev);
//	kfree(char_dev);
	unregister_chrdev_region(gpio_dev_t,1);
	
	
	gpio_set_value(led_gpio, 0);
	gpio_free(led_gpio);
	return 0;
}

#ifdef CONFIG_PM
static int earlight_suspend(struct device *dev) {
	if(led_gpio) gpio_set_value(led_gpio,0);
	return 0;
}

static int earlight_resume(struct device *dev) {
	if(led_gpio) gpio_set_value(led_gpio,!!light);
	return 0;
}

static const struct dev_pm_ops earlight_pm = {
        .suspend = earlight_suspend,
        .resume = earlight_resume,
};

#define earlight_pm_ops (&earlight_pm)


#else                           /* CONFIG_PM */

#define earlight_pm_ops NULL

#endif                          /* CONFIG_PM */

static struct platform_driver earlight_driver = {
        .driver = {
                   .name = "earlight",
                   .of_match_table = of_match_ptr(earlight_of_match),
                   .pm = earlight_pm_ops,
                   },
        .probe = earlight_probe,
        .remove = earlight_remove,
};

module_platform_driver(earlight_driver);

MODULE_LICENSE("GPL v2");

–>测试方法
开灯:echo “1” > /dev/earlight
关灯:echo “0” > /dev/earlight

–>拓展功能
android从按电源键到进入系统播放开机音乐需要好多秒的时间,带屏还好,屏亮了就知道系统起来了。如果不带屏的呢,这种产品也有许多,如机顶盒。甲方就会反馈,我怎么按半天机器才起来。其实这时候早就起来了,只是进入系统花了点时间。这时候就要改uboot的代码。个人思路,开机两秒之内屏就会亮,可以在这部分代码加上亮灯操作。以全志代码修改为例:

--- longan/brandy/brandy-2.0/u-boot-2018/drivers/video/sunxi/disp2/disp/dev_disp.c      (revision 5310)
+++ longan/brandy/brandy-2.0/u-boot-2018/drivers/video/sunxi/disp2/disp/dev_disp.c      (working copy)
@@ -630,6 +630,19 @@
        g_disp_drv.inited = true;
 
        tick_printf("%s finish\n", __func__);

+       user_gpio_set_t ear_gpio;
+       u32 gpio0_handle = 0;
+       if(fdt_get_one_gpio("/soc/earlight", "gpio_en", &ear_gpio) == 0) {
+               gpio0_handle = sunxi_gpio_request(&ear_gpio, 1);
+               gpio_write_one_pin_value(gpio0_handle ,1, "gpio_en");
+
+       } else {
+               tick_printf("get /soc/earlight/gpio_en error!!!\n");
+       }
+
 
 exit:
        return ret;

拓展
以上示例程序代码只是控制一个灯,如果是几个灯呢,如果是做呼吸灯,是不是要控制呼吸长短等参数,这是就需要传好多个参数,怎么办。一种办法是实现unlocked_ioctl函数,通过ioctl的方式,但是这种方式就要自己写C语言来测试,app控制还要实现JNI,有点麻烦。为了不加班,还是在read/write函数实现控制,以下示例:

ssize_t aw9106b_read (struct file *file, char __user *buf,size_t count, loff_t *ppos) {
	int p;
	char *str = "Parameter 1:led1,1:on,0:off\n"
				"Parameter 2:led2,1:on,0:off\n"
				"Parameter 3:led3,1:on,0:off\n"
				"Parameter 4:led4,1:on,0:off\n"
				"Parameter 5:led5,1:on,0:off\n"
				"Parameter 6:led6,1:on,0:off\n"
				"Parameter 7:fade in and fade out time setting level 0-5\n"
				"Parameter 8:full light and full dark time setting level 0-7\n"
				"ps:1,1,1,1,1,1,3,2\n";
	
	//printk("(strlen(str)=%d, count=%ld, *ppos=%d\n",strlen(str),count,*ppos);

	if(*ppos >= strlen(str)) return 0;
	
	p = count + *ppos;
	if((p - strlen(str)) >= 0){
		copy_to_user(buf, str + (*ppos),  strlen(str));
		*ppos += strlen(str);
		return  strlen(str);
	} else {
		copy_to_user(buf, str + (*ppos), count);
		*ppos += count;
		return count;
	}
 }

ssize_t aw9106b_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
	char vbuff[30];
	u8 i,ret;
	
	/* init vbuf */
	for(i=0;i<sizeof(vbuff);i++) vbuff[i] = '\0';
	
	ret = copy_from_user(vbuff, buf, count);
	if(ret < 0) {
        printk(KERN_ERR"%s: copy_from_user error!!!\n",__func__);
        return count;
    }
	printk(KERN_ERR"%s: vbuff = %s\n",__func__,vbuff);
	
	ret = sscanf(vbuff,"%d,%d,%d,%d,%d,%d,%d,%d",&on_led0_r,&on_led0_g,&on_led0_b,
												&on_led1_r,&on_led1_g,&on_led1_b,
												&rise_time,&hold_time);
	if(ret < 0) {
        printk(KERN_ERR"%s: Parameter is invalid!!!\n",__func__);
        return count;
    }

	on_led0_r = !!on_led0_r;
	on_led0_g = !!on_led0_g;
	on_led0_b = !!on_led0_b;
	on_led1_r = !!on_led1_r;
	on_led1_g = !!on_led1_g;
	on_led1_b = !!on_led1_b;
												
	printk("on_led0_r=%d,on_led0_g=%d,on_led0_b=%d,on_led1_r=%d,on_led1_g=%d,on_led1_b=%d,rise_time=%d,hold_time=%d\n",  
									on_led0_r,on_led0_g,on_led0_b,
									on_led1_r,on_led1_g,on_led1_b,
									rise_time,hold_time);
	
	if(on_led0_r==on_led0_g==on_led0_b==on_led1_r==on_led1_g==on_led1_b==0){
		printk(KERN_ERR"%s:off all led!!!\n",__func__);
		AW9106_OnOff(0);
		return count;
	}

	if(rise_time < 0 || rise_time > 5) {
		printk(KERN_ERR"%s:rise_time set error!!!\n",__func__);
		return count;
	}

	if(hold_time < 0 || hold_time > 7) {
		printk(KERN_ERR"%s:hold_time set error!!!\n",__func__);
		return count;
	}

	AW9106_breath_ctrl();
	
	return count;
}

echo “1,1,1,1,1,1,3,2” > /dev/led_xxx实现控制,参数有点复杂,时间久了,忘记了参数1,2,3,…是什么了,去服务器找代码看就太费事了,cat /dev/led_xxx看一下参数说明。

14.PWM控制灯光亮度例程
–>dts配置

&soc{
	led_pwm:led_pwm{
		compatible = "allwinner,led_pwm";
		pwm_num = <1>;
		status = "okay";
	};
	
	pwm1: pwm1@0300a000 {
		pinctrl-names = "active", "sleep";
		pinctrl-0 = <&pwm1_pin_a>;
		pinctrl-1 = <&pwm1_pin_b>;
	};

};

&pio{
	pwm1_pin_a: pwm1@0 {
		allwinner,pins = "PB10";
		allwinner,function = "pwm1";
		allwinner,muxsel = <0x04>;
		allwinner,drive = <0x2>;
		allwinner,pull = <0>;
		allwinner,data = <0xffffffff>;
	};

	pwm1_pin_b: pwm1@1 {
		allwinner,pins = "PB10";
		allwinner,function = "io_disabled";
		allwinner,muxsel = <0x07>;
		allwinner,drive = <0x2>;
		allwinner,pull = <0>;
		allwinner,data = <0xffffffff>;
	};
};

–>驱动代码

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/pwm.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define LED_PWM_DEVICE_NAME "led_pwm"
#define LEVEL (10)

static int pwm_id = -1;
static const int period = 20*1000*1000;
static uint8_t dr = LEVEL/2;
static struct pwm_device *pwm;
static int8_t is_run = 0;

static dev_t gpio_dev_t;
static struct cdev *char_dev;
static struct class *cls;
static struct device *dev;

static const struct of_device_id led_pwm_of_match[] = {
        {.compatible = "allwinner,led_pwm",},
        { /* sentinel */ }
};

static void ctrl_pwm(int mdr) {
	if(mdr>=0 && is_run){
		pwm_config(pwm, period * mdr/LEVEL, period);
	} if(mdr > 0 && !is_run){
		pwm_config(pwm, period * mdr/LEVEL, period);
		pwm_enable(pwm);
		is_run = 1;
	} /*else if(mdr == 0 && is_run) {
		pwm_disable(pwm);
		is_run = 0;
	}*/
}

static int led_pwm_open (struct inode *inode, struct file *filp){
	return 0;
}

ssize_t led_pwm_read (struct file *file, char __user *buf,size_t count, loff_t *ppos) {
	
	copy_to_user(buf, &dr, sizeof(dr));
	
	return sizeof(dr);
 }

ssize_t led_pwm_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
	char vbuff[5] = {'\0','\0','\0','\0','\0'};
	int value = 0;
	int err = -1;
	
	if(count > 4) {
		printk(KERN_ERR"%s:too large to write !!!\n",__func__);
		return count;
	}
	
	copy_from_user(vbuff, buf, count);
	
	if(count == 1){
		value = vbuff[0];
		printk(KERN_ERR"%s:value= %d\n",__func__,value);
		if(value>=0 && value<=10){
			dr = (uint8_t)value;
		} else if(value>=0x30 && value<=(10+0x30)){
			dr = (uint8_t)value - 0x30;
		}
		
		ctrl_pwm(dr);

		
		return count;
	}
	
	//printk(KERN_ERR"%s: vbuff = %s\n",__func__,vbuff);
	err = sscanf(vbuff,"%d",&value);
	if(err < 0) {
		printk(KERN_ERR"%s: value is invalid!!!\n",__func__);
		return count;
	}
	printk(KERN_ERR"%s:value= %d\n",__func__,value);
	if(value>=0 && value<=10){
		dr = (uint8_t)value;
	}
	
	ctrl_pwm(dr);
	return count;
}

static int led_pwm_close (struct inode *inode, struct file *filp){
	return 0;	
}

static struct file_operations fops = {
	.owner          = THIS_MODULE,
	.open           = led_pwm_open,
	.read			= led_pwm_read,
	.write			= led_pwm_write,
	.release        = led_pwm_close,
};

static int led_pwm_probe(struct platform_device *pdev) {
	struct device_node *np = pdev->dev.of_node;
	int ret = -1;
	
	if (!np) {
        dev_err(&pdev->dev, "laser: Device Tree np missing\n");
        return -EINVAL;
    }
    
    ret = of_property_read_u32(np, "pwm_num", &pwm_id);
    if (ret < 0) {
        dev_err(&pdev->dev, "laser: pwm-id missing\n");
        return ret;
    }
	
	printk("led_pwm: use pwm id is %d\n",pwm_id);

	pwm = pwm_request(pwm_id, "led-pwm");
	if (IS_ERR(pwm)) {
        dev_err(&pdev->dev, "led_pwm: unable to request legacy PWM %d\n",pwm_id);
        ret = PTR_ERR(pwm);
        return ret;
    }
	
	pwm_config(pwm, period * dr/LEVEL, period);
	ret = pwm_set_polarity(pwm, PWM_POLARITY_INVERSED);
	//ret = pwm_set_polarity(pwm, PWM_POLARITY_NORMAL);
    if (ret < 0)
        printk("pwm set polarity fail, ret = %d\n", ret);
	//pwm_enable(pwm);
	is_run = 0;
	
	
	/* 申请设备号 */
	ret = alloc_chrdev_region(&gpio_dev_t,0,1,LED_PWM_DEVICE_NAME);
	if(ret < 0){
		printk(KERN_ERR"dev_t request error!\n");
		goto chrdev_alloc_error;
	}
	
	/* 分配cdev */
	char_dev = cdev_alloc();
	if (NULL == char_dev || IS_ERR(char_dev)) {
		printk(KERN_ERR"char_dev fail!\n");
		goto cdev_alloc_error;
	}
	
	/* 初始化cdev */
	cdev_init(char_dev, &fops);
	
	/* 注册cdev */
	ret = cdev_add(char_dev, gpio_dev_t, 1);
	if(ret < 0){
		printk(KERN_ERR"cdev add error!\n");
		goto cdev_add_error;
	}
	
	/* 创建设备节点 */
	cls = class_create(THIS_MODULE, LED_PWM_DEVICE_NAME);
	dev = device_create(cls, NULL, gpio_dev_t, NULL, LED_PWM_DEVICE_NAME);
	if (NULL == dev || IS_ERR(dev)) {
		printk(KERN_ERR"device_create fail!\n");
		goto device_creat_error;
	}
	
	return 0;

/* 异常处理 */
device_creat_error:
	class_destroy(cls);
	cdev_del(char_dev);
	
cdev_add_error:
	kfree(char_dev);
cdev_alloc_error:
	unregister_chrdev_region(gpio_dev_t,1);
	
chrdev_alloc_error:
	pwm_disable(pwm);
    pwm_free(pwm);
	return -1;
}

static int led_pwm_remove(struct platform_device *pdev) {
	device_del(dev);
	class_destroy(cls);

	cdev_del(char_dev);
	kfree(char_dev);
	unregister_chrdev_region(gpio_dev_t,1);
	
	pwm_disable(pwm);
    pwm_free(pwm);
	return 0;
}

#ifdef CONFIG_PM
static int led_pwm_suspend(struct device *dev) {
	
	return 0;
}

static int led_pwm_resume(struct device *dev) {
	
	return 0;
}

static const struct dev_pm_ops led_pwm_pm = {
        .suspend = led_pwm_suspend,
        .resume = led_pwm_resume,
};

#define led_pwm_pm_ops (&led_pwm_pm)

#else                           /* CONFIG_PM */

#define led_pwm_pm_ops NULL

#endif                          /* CONFIG_PM */

static struct platform_driver led_pwm_driver = {
        .driver = {
                   .name = "led_pwm",
                   .of_match_table = of_match_ptr(led_pwm_of_match),
                   .pm = led_pwm_pm_ops,
                   },
        .probe = led_pwm_probe,
        .remove = led_pwm_remove,
};

module_platform_driver(led_pwm_driver);
MODULE_LICENSE("GPL v2");
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值