重要参考文档:
1.Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
2.Documentation/devicetree/bindings/gpio/gpio-samsung.txt
3.Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
从原理图可以得知beep的GPIO是GPD0_0
设计到寄存器如下
1.GPD0CON Base Address: 0x1140_0000 Address = Base Address + 0x00A0
2.GPD0DAT Base Address: 0x1140_0000 Address = Base Address + 0x00A4
3. GPD0PUD Base Address: 0x1140_0000 Address = Base Address + 0x00A8
4.GPD0DRV Base Address: 0x1140_0000 Address = Base Address + 0x00AC
设备树里面
pinctrl@11400000 {
beep_gpio {
samsung,pins = "gpd0-1";
samsung,pin-function = <0x1>; /* output */
samsung,pin-pud = <0x1>; /* Enables Pull-down */
samsung,pin-drv = <0x1>; /* x4 */
phandle = <0x51>;
};
};
/{
...
beep {
compatible = "tiny4412,beep";
reg = <0x114000A0 0x20>;
tiny4412,gpio = <0x52 0x0 1>; // default low (1)
pinctrl-names = "default";
pinctrl-0 = <0x51>;
};
...
};
这里设计到pinctrl的东西先掠过理论,只是把里面的内容搞明白。
参考Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
1. samsung,pins
The child node should contain a list of pin(s) on which a particular pin
function selection or pin configuration (or both) have to applied. This
list of pins is specified using the property name "samsung,pins". There
should be atleast one pin specfied for this property and there is no upper
limit on the count of pins that can be specified. The pins are specified
using pin names which are derived from the hardware manual of the SoC. As
an example, the pins in GPA0 bank of the pin controller can be represented
as "gpa0-0", "gpa0-1", "gpa0-2" and so on. The names should be in lower case.
The format of the pin names should be (as per the hardware manual)
"[pin bank name]-[pin number within the bank]".
2. samsung,pin-function
The pin function selection that should be applied on the pins listed in the
child node is specified using the "samsung,pin-function" property. The value
of this property that should be applied to each of the pins listed in the
"samsung,pins" property should be picked from the hardware manual of the SoC
for the specified pin group. This property is optional in the child node if
no specific function selection is desired for the pins listed in the child
node. The value of this property is used as-is to program the pin-controller
function selector register of the pin-bank.
3. samsung,pin-pud: Pull up/down configuration.
4. samsung,pin-drv: Drive strength configuration.
以上两个要看具体的硬件
The values specified by these config properties should be derived from the
hardware manual and these values are programmed as-is into the pin
pull up/down and driver strength register of the pin-controller.
文章后面有个例子
uart0_data: uart0-data {
samsung,pins = "gpa0-0", "gpa0-1";
samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;
samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;
};
另外再参考Documentation/devicetree/bindings/gpio/gpio-samsung.txt
The syntax of the gpio specifier used by client nodes
should be the following with values derived from the SoC user manual.
<[phandle of the gpio controller node]
[pin number within the gpio controller]
[mux function]
[flags and pull up/down]
[drive strength]>
Values for gpio specifier:
- Pin number: is a value between 0 to 7.
- Flags and Pull Up/Down: 0 - Pull Up/Down Disabled.
1 - Pull Down Enabled.
3 - Pull Up Enabled.
Bit 16 (0x00010000) - Input is active low.
- Drive Strength: 0 - 1x,
1 - 3x,
2 - 2x,
3 - 4x
另外节后数据手册,不难理解。
以上是pinctrl相关,那对于具体的器件呢,如上面的beep节点
beep {
compatible = "tiny4412,beep";
reg = <0x114000A0 0x20>;
tiny4412,gpio = <0x52 0x0 1>; // default low (1)
pinctrl-names = "default";
pinctrl-0 = <0x51>;
};
对于compatible主要是驱动程序匹配时使用,必须与驱动里面的相同
const struct of_device_id tiny4412_beep_match_tbl[] =
{
{.compatible = "tiny4412,beep"},
{},
};
对于reg指的是其gpio地址内存,即gpd0的地址,大小时0x20
对于tiny4412,gpio = <0x52 0x0 1>; 其中
·0x52 : [phandle of the gpio controller node], 这里无疑会指向pinctrl里面的gpd0
·0x0 : [pin number within the gpio controller]
·0x0 : [flags] (0 - Active High , 1 - Active Low)
对于pinctrl-names, 和pinctrl-0,这两个联系很紧密, 参考Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
pinctrl-names里面定义多个字符串,表示多个状态,例如default, active, sleep..., 字符串时自定义的,而这些排列的字符串将会分别对应pinctrl-0, pinctrl-1, pinctrl-2, ...
驱动里面会使用这个names来区分不同的状态,从而调用pinctrl-x
Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt里面说
== Pinctrl client devices ==
For each client device individually, every pin state is assigned an integer
ID. These numbers start at 0, and are contiguous. For each state ID, a unique
property exists to define the pin configuration. Each state may also be
assigned a name. When names are used, another property exists to map from
those names to the integer IDs.
Each client device's own binding determines the set of states that must be
defined in its device tree node, and whether to define the set of state
IDs that must be provided, or whether to define the set of state names that
must be provided.
...
Required properties:
...
pinctrl-n: List of phandles, each pointing at a pin configuration
node within a pin controller.
pinctrl-names: The list of names to assign states. List entry 0 defines the
name for integer state ID 0, list entry 1 for state ID 1, and
so on.
后面还有一个例子
/*
* For an IP block whose binding supports pin configuration,
* but in use on an SoC that doesn't have any pin control hardware
*/
device {
pinctrl-names = "active", "idle";
pinctrl-0 = <>;
pinctrl-1 = <>;
};
驱动beep_dt.c
/*
* beep_dt driver for tiny4412 on linux-4.19.27
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h> //
#include <linux/io.h>
#include <linux/gfp.h>
#include <linux/cdev.h>
#include <linux/uaccess.h> /* copy_from_usr, */
struct beep_data
{
char name[32];
int gpio;
int state;
struct pinctrl *pctrl;
struct pinctrl_state *def_state;
};
/*
* resource
* GPD0_0 for buzzer
*
* Note(s) : GPD0_1 as LCD
*/
struct _gpio
{
u32 __iomem con; /* GPM0 configuration register */
u32 __iomem data; /* GPM0 data register */
u32 __iomem pud; /* GPM0 pull-up/ pull-down register */
u32 __iomem drv; /* GPM0 drive strength control register */
u32 __iomem conpdn; /* GPM0 power down mode configuration register */
u32 __iomem pudpdn; /* GPM0 power down mode pull-up/ pull-down register 0x000 */
};
static struct _gpio *beep_gpio = NULL;
#define DEV_NAME "beep"
struct beep_dev
{
struct cdev *cdev;
dev_t dev;
int major;
char name[32];
struct class *class;
};
static struct beep_dev *beep_dev = NULL;
static int beep_open (struct inode *inode, struct file *file)
{
printk(KERN_INFO "open \n");
return 0;
}
static int beep_close (struct inode *inode, struct file *file)
{
printk(KERN_INFO "close \n");
return 0;
}
static ssize_t beep_write (struct file *file, const char __user *usrbuf, size_t len, loff_t *off)
{
char cmd,opt;
char rev_buf[8] = {0};
printk(KERN_INFO "write \n");
if(copy_from_user(rev_buf,usrbuf,8))
return -EFAULT;
cmd = rev_buf[0];
opt = rev_buf[1];
printk(KERN_NOTICE "cmd : %d opt : %d \n", cmd, opt);
if(cmd == 0)
{
// off
beep_gpio->data &= ~(1<<0);
}
else if(cmd == 1)
{
// on
beep_gpio->data |= (1<<0);
}
else
{
// do nothing.
}
return 0;
}
static long beep_ioctl (struct file *file, unsigned int cmd, unsigned long arg)
{
printk(KERN_INFO "ioctl \n");
return 0;
}
const struct file_operations beep_ops =
{
.owner = THIS_MODULE,
.open = beep_open,
.release = beep_close,
.unlocked_ioctl = beep_ioctl,
.write = beep_write,
};
static ssize_t beep_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return 0;
}
static ssize_t beep_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct beep_data *data = dev_get_drvdata(dev);
char state[16];
int value = 0;
if (sscanf(buf, "%s", state) != 1)
return -EINVAL;
if (sscanf(buf, "%d", &value)) {
pr_notice("enter %s value : %d \n", __func__, value);
if (value & (1 << 0))
gpio_set_value(data->gpio, 1);
else
gpio_set_value(data->gpio, 0);
}
return count;
}
static DEVICE_ATTR(beep, 0644, beep_show,
beep_store);
static int chrdev_setup(void)
{
int ret = 0;
beep_dev = kmalloc(sizeof(struct beep_dev),GFP_KERNEL);
if(!beep_dev)
return -ENOMEM;
strcpy(beep_dev->name, DEV_NAME);
ret = alloc_chrdev_region(&beep_dev->dev, 0, 1, beep_dev->name);
beep_dev->major = MAJOR(beep_dev->dev);
if(ret < 0)
{
kfree(beep_dev);
return ret;
}
beep_dev->cdev = cdev_alloc();
if(!beep_dev->cdev)
{
unregister_chrdev_region(beep_dev->dev,1);
kfree(beep_dev);
return -EFAULT;
}
cdev_init(beep_dev->cdev,&beep_ops);
beep_dev->cdev->owner = THIS_MODULE;
beep_dev->cdev->ops = &beep_ops;
cdev_add(beep_dev->cdev,beep_dev->dev,1);
beep_dev->class = class_create(THIS_MODULE,beep_dev->name);
ret = PTR_ERR(beep_dev->class);
if (IS_ERR(beep_dev->class))
{
cdev_del(beep_dev->cdev);
unregister_chrdev_region(beep_dev->dev,1);
kfree(beep_dev);
return -EFAULT;
}
device_create(beep_dev->class,NULL,beep_dev->dev,NULL,beep_dev->name,beep_dev);
return 0;
}
static void chrdev_remove(void)
{
device_destroy(beep_dev->class, beep_dev->dev);
class_destroy(beep_dev->class);
cdev_del(beep_dev->cdev);
unregister_chrdev_region(beep_dev->dev,1);
kfree(beep_dev);
}
static int beep_probe(struct platform_device *pdev)
{
int ret = 0;
int gpio = -1;
struct beep_data *data = NULL;
struct resource *res = NULL;
struct device *dev = &pdev->dev;
pr_notice("enter %s\n", __func__);
if (!dev->of_node) {
dev_err(dev, "no platform data.\n");
return -EINVAL;
}
ret = chrdev_setup();
if(ret){
dev_err(dev, "setup error .\n");
return -EINVAL;
}
data = devm_kzalloc(dev, sizeof(struct beep_data), GFP_KERNEL);
if(!data)
{
dev_err(dev, "no memory.\n");
return -ENOMEM;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if(!res){
dev_err(dev, "no resource.\n");
devm_kfree(dev, data);
return -ENOMEM;
}
beep_gpio = (struct _gpio *)devm_ioremap(dev, res->start, res->end - res->start);
if(!beep_gpio){
dev_err(dev, "ioremap error.\n");
devm_kfree(dev, data);
return -EINVAL;
}
gpio = of_get_named_gpio(dev->of_node, "tiny4412,gpio", 0);
if (gpio < 0) {
dev_err(dev, "Looking up %s (%d) property in node %s failed %d\n",
"tiny4412,gpio", 0, dev->of_node->full_name, gpio);
goto err;
}
printk("%s, cfg: 0x%x,data: 0x%x, pud: 0x%x, drv: 0x%x\n", __func__,
readl(&beep_gpio->con),readl(&beep_gpio->data), readl(&beep_gpio->pud), readl(&beep_gpio->drv));
data->gpio = gpio;
if (gpio_is_valid(gpio)) {
sprintf(data->name, "tiny4412.beep%d", 0);
ret = devm_gpio_request_one(dev, gpio, GPIOF_INIT_LOW, data->name);
if (ret < 0) {
dev_err(dev, "request gpio %d failed.\n", gpio);
goto err;
}
}
printk("%s, cfg: 0x%x,data: 0x%x, pud: 0x%x, drv: 0x%x\n", __func__,
readl(&beep_gpio->con),readl(&beep_gpio->data), readl(&beep_gpio->pud), readl(&beep_gpio->drv));
data->pctrl = devm_pinctrl_get(dev); // get pinctrl
if (IS_ERR(data->pctrl)) {
dev_err(dev, "pinctrl get failed.\n");
devm_gpio_free(dev, gpio);
goto err;
}
data->def_state = pinctrl_lookup_state(data->pctrl, "default");
if (IS_ERR(data->def_state)) {
dev_err(dev, "look up sleep state failed.\n");
devm_pinctrl_put(data->pctrl);
devm_gpio_free(dev, gpio);
goto err;
}
dev_set_drvdata(dev, data);
// /sys/bus/platform/drivers/leds/110002e0.led 下面生成led节点
// 可以使用echo 1 > ./led写指令控制led, 最终调用leds_store达到控制的目的
device_create_file(dev, &dev_attr_beep);
pinctrl_select_state(data->pctrl, data->def_state);
data->state = 1;
printk("%s, cfg: 0x%x,data: 0x%x, pud: 0x%x, drv: 0x%x\n", __func__,
readl(&beep_gpio->con),readl(&beep_gpio->data), readl(&beep_gpio->pud), readl(&beep_gpio->drv));
return 0;
err:
devm_iounmap(dev, beep_gpio);
devm_kfree(dev, data);
return -EINVAL;
}
static int beep_remove(struct platform_device *pdev)
{
pr_notice("enter %s\n", __func__);
device_remove_file(&pdev->dev, &dev_attr_beep);
chrdev_remove();
return 0;
}
const struct of_device_id tiny4412_beep_match_tbl[] =
{
{.compatible = "tiny4412,beep"},
{},
};
static struct platform_driver tiny4412_beep_dt_drv =
{
.probe = beep_probe,
.remove = beep_remove,
.driver =
{
.owner = THIS_MODULE,
.name = "beep",
.of_match_table = tiny4412_beep_match_tbl,
}
};
static int __init beep_dt_init(void)
{
int ret = 0;
ret = platform_driver_register(&tiny4412_beep_dt_drv);
if(ret)
printk(KERN_ERR "init demo: probe failed: %d\n", ret);
return ret;
}
static void __exit beep_dt_exit(void)
{
platform_driver_unregister(&tiny4412_beep_dt_drv);
}
module_init(beep_dt_init);
module_exit(beep_dt_exit);
MODULE_LICENSE("GPL");
Makefile
TARGET := beep_dt
obj-m += $(TARGET).o
BIN_PATH = /home/flinn/bin/tiny4412/
ROOTFS = /home/flinn/tmp/rootfs
KERNEL = /home/flinn/tiny4412-SDK/linux-4.19.27
DT = exynos4412-tiny4412
all:
make -C $(KERNEL) M=`pwd` modules
clean:
make -C $(KERNEL) M=`pwd` clean
install:
#make -C $(KERNEL) M=`pwd` modules_install INSTALL_MOD_PATH=$(ROOTFS)
sudo cp $(TARGET).ko $(ROOTFS)
sudo cp $(DT).dtb $(BIN_PATH)
dtbs:
#dtc -I dtb -O dts tiny4412.dts -o tiny4412.dtb
dtc -O dtb -o $(DT).dtb $(DT).dts
最终,进入文件系统后
在 /sys/bus/platform/drivers/beep/114000a0.beep目录下会生成beep节点。执行
echo 1 > beep // 蜂鸣器会响
echo 0 > beep // 蜂鸣器会停止响