【硬件平台】
CPU:S905D3 GPIOH_5 PWM_F(func4) 背光芯片:SGM3732YTN6G/TR
【底层控制】
1、dts设备树 1)芯片PWM实体描述 common/arch/arm64/boot/dts/amlogic/mesonsm1.dtsi pwm参考文档:common/Documentation/devicetree/bindings/pwm/pwm.txt
/ {
soc {
compatible = "simple-bus";
#address-cells = <2>;
#size-cells = <2>;
ranges;
cbus: cbus@ffd00000 {
compatible = "simple-bus";
reg = <0x0 0xffd00000 0x0 0x26000>;
#address-cells = <2>;
#size-cells = <2>;
ranges = <0x0 0x0 0x0 0xffd00000 0x0 0x26000>;
pwm_ef: pwm@19000 {
compatible = "amlogic,g12a-ee-pwm";
reg = <0x0 0x19000 0x0 0x20>;
#pwm-cells = <3>; ---》表示在其他地方可以引用pwm,并且可以传递3个参数
clocks = <&xtal>,
<&xtal>,
<&xtal>,
<&xtal>;
clock-names = "clkin0",
"clkin1",
"clkin2",
"clkin3";
status = "disabled";
};
common/arch/arm64/boot/dts/amlogic/fj-a2-n1904.dts
/ {
...
lcd-pwm-backlight {
compatible = "lcd-pwm-backlight";
status = "okay";
pwms = <&pwm_ef MESON_PWM_1 1000000 0>;
};
};
&pwm_ef {
status = "okay";
};
2、驱动代码 1)厂家pwm控制器驱动代码 common/drivers/amlogic/pwm/pwm_meson.c
static const struct pwm_ops meson_pwm_ops = {
.request = meson_pwm_request,
.free = meson_pwm_free,
.apply = meson_pwm_apply,
.get_state = meson_pwm_get_state,
.owner = THIS_MODULE,
};
static int meson_pwm_probe(struct platform_device *pdev)
{
struct meson_pwm_channel *channels;
struct meson_pwm *meson;
struct resource *regs;
int err;
meson = devm_kzalloc(&pdev->dev, sizeof(*meson), GFP_KERNEL);
if (!meson)
return -ENOMEM;
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
meson->base = devm_ioremap_resource(&pdev->dev, regs);
if (IS_ERR(meson->base))
return PTR_ERR(meson->base);
mutex_init(&meson->lock);
spin_lock_init(&meson->pwm_lock);
meson->chip.dev = &pdev->dev;
meson->chip.ops = &meson_pwm_ops;
meson->chip.base = -1;
meson->chip.of_xlate = of_pwm_xlate_with_flags;
meson->chip.of_pwm_n_cells = 3;
meson->data = (struct meson_pwm_data *)
of_device_get_match_data(&pdev->dev);
if (meson->data->double_channel)
meson->chip.npwm = 4;
else
meson->chip.npwm = 2;
meson->inverter_mask = BIT(meson->chip.npwm) - 1;
channels = devm_kcalloc(&pdev->dev, meson->chip.npwm, sizeof(*channels),
GFP_KERNEL);
if (!channels)
return -ENOMEM;
err = meson_pwm_init_channels(meson, channels);
if (err < 0) {
dev_err(&pdev->dev, "failed to init PWM channels: %d\n", err);
return err;
}
err = pwmchip_add(&meson->chip);
if (err < 0) {
dev_err(&pdev->dev, "failed to register PWM chip: %d\n", err);
return err;
}
if (meson->data->double_channel)
meson_pwm_sysfs_init(&pdev->dev);
meson_pwm_add_channels(meson, channels);
platform_set_drvdata(pdev, meson);
return 0;
}
2)内核pwm测试代码 放在目录:common/driver/amlogic/pwm,并添加到该目录下的Makefile中
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#define PWM_LASER_SET_PERIOD _IOW('A', 0x11, unsigned int)
#define PWM_LASER_GET_PERIOD _IOR('A', 0x12, unsigned int)
#define PWM_LASER_SET_DUTY _IOW('A', 0x13, unsigned int)
#define PWM_LASER_GET_DUTY _IOR('A', 0x14, unsigned int)
#define PWM_LASER_ENABLE _IO('A', 0x15)
#define PWM_LASER_DISABLE _IO('A', 0x16)
#define PWM_DEFAULT_DUTY_NS 1500000
#define PWM_DEFAULT_PERIOD_NS 2000000
struct pwm_para {
unsigned int period;
unsigned int level;
unsigned int duty;
struct pwm_device *pwm;
};
struct pwm_para laser;
static void pwm_laser_update(void)
{
laser.duty = laser.level * laser.period / 100;
printk("[%s] line%d, duty=%d\n", __FUNCTION__, __LINE__, laser.duty);
pwm_config(laser.pwm, laser.duty, laser.period);
}
static void pwm_laser_enable(void)
{
printk("[%s] line%d\n", __FUNCTION__, __LINE__);
pwm_enable(laser.pwm);
}
static void pwm_laser_disable(void)
{
printk("[%s] line%d\n", __FUNCTION__, __LINE__);
pwm_disable(laser.pwm);
}
static void pwm_laser_set_period(unsigned int period)
{
laser.period = period * 1000000;
printk("[%s] line%d period=%d\n", __FUNCTION__, __LINE__, laser.period);
pwm_set_period(laser.pwm, laser.period);
}
static unsigned int pwm_laser_get_period(void)
{
return (laser.period / 1000000);
}
static int pwm_laser_open(struct inode *inode, struct file *filp)
{
printk("[%s] line%d\n", __FUNCTION__, __LINE__);
return 0;
}
static int pwm_laser_release(struct inode *inode, struct file *filp)
{
printk("[%s] line%d\n", __FUNCTION__, __LINE__);
return 0;
}
static ssize_t pwm_laser_read(struct file *filp, char __user *buf, size_t len, loff_t *pos)
{
printk("[%s] line%d\n", __FUNCTION__, __LINE__);
return 0;
}
static ssize_t pwm_laser_write(struct file *filp, const char __user *buf, size_t len, loff_t *pos)
{
printk("[%s] line%d\n", __FUNCTION__, __LINE__);
return 0;
}
static long pwm_laser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned int period;
unsigned int level;
void __user *argp = (void __user *)arg;
printk("[%s] line%d cmd=0x%x\n", __FUNCTION__, __LINE__, cmd);
switch (cmd) {
case PWM_LASER_SET_PERIOD:
if (argp == NULL) {
printk("laser: invalid argument.");
return -EINVAL;
}
if (copy_from_user(&period, argp, sizeof(unsigned int))) {
printk("copy_from_user failed.");
return -EFAULT;
}
pwm_laser_set_period(period);
break;
case PWM_LASER_GET_PERIOD:
period = pwm_laser_get_period();
if (copy_to_user(argp, &period, sizeof(unsigned int))) {
printk("copy_to_user failed.");
return -EFAULT;
}
break;
case PWM_LASER_SET_DUTY :
if (argp == NULL) {
printk("laser: invalid argument.");
return -EINVAL;
}
if (copy_from_user(&level, argp, sizeof(unsigned int))) {
printk("copy_from_user failed.");
return -EFAULT;
}
if ((level < 0) || (level > 100)) {
printk("laser: invalid argument.");
return -EINVAL;
}
laser.level = level;
pwm_laser_update();
break;
case PWM_LASER_GET_DUTY :
if (copy_to_user(argp, &laser.level, sizeof(unsigned int))) {
printk("copy_to_user failed.");
return -EFAULT;
}
break;
case PWM_LASER_ENABLE:
pwm_laser_update();
pwm_laser_enable();
break;
case PWM_LASER_DISABLE:
pwm_laser_disable();
break;
default:
printk("laser: cmd error!\n");
return -EFAULT;
}
return 0;
}
struct file_operations pwm_laser_fops = {
.owner = THIS_MODULE,
.open = pwm_laser_open,
.release = pwm_laser_release,
.write = pwm_laser_write,
.read = pwm_laser_read,
.unlocked_ioctl = pwm_laser_ioctl,
};
struct miscdevice pwm_laser_dev =
{
.minor = MISC_DYNAMIC_MINOR,
.fops = &pwm_laser_fops,
.name = "lcd-pwm-backlight",
};
static int pwm_laser_probe(struct platform_device *pdev)
{
int ret;
printk("[%s] line%d entry\n", __FUNCTION__, __LINE__);
laser.pwm = devm_pwm_get(&pdev->dev, NULL);
if (laser.pwm == NULL) {
dev_err(&pdev->dev, "laser: unable to request PWM device\n");
ret = PTR_ERR(laser.pwm);
return ret;
}
pwm_config(laser.pwm, PWM_DEFAULT_DUTY_NS, PWM_DEFAULT_PERIOD_NS);
pwm_enable(laser.pwm);
misc_register(&pwm_laser_dev);
printk("[%s] line%d success\n", __FUNCTION__, __LINE__);
return 0;
}
static int pwm_laser_remove(struct platform_device *pdev)
{
printk("[%s] line%d\n", __FUNCTION__, __LINE__);
pwm_disable(laser.pwm);
pwm_free(laser.pwm);
misc_deregister(&pwm_laser_dev);
return 0;
}
int pwm_laser_suspend(struct device *dev)
{
printk("[%s] line%d\n", __FUNCTION__, __LINE__);
return 0;
}
int pwm_laser_resume(struct device *dev)
{
printk("[%s] line%d\n", __FUNCTION__, __LINE__);
return 0;
}
const struct dev_pm_ops pwm_laser_pm_ops = {
.suspend = pwm_laser_suspend,
.resume = pwm_laser_resume,
};
static const struct of_device_id of_pwm_laser_match[] = {
{.compatible = "lcd-pwm-backlight", },
{},
};
static struct platform_driver pwm_laser = {
.probe = pwm_laser_probe,
.remove = pwm_laser_remove,
.driver = {
.name = "lcd-pwm-backlight",
.owner = THIS_MODULE,
.pm = &pwm_laser_pm_ops,
.of_match_table = of_match_ptr(of_pwm_laser_match),
},
};
module_platform_driver(pwm_laser);
MODULE_DESCRIPTION("pwm control driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
3)用户态pwm验证代码 编译命令:/opt/gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc -o test --static pwm_test.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#define PWM_LASER_SET_PERIOD _IOW('A', 0x11, unsigned int)
#define PWM_LASER_GET_PERIOD _IOR('A', 0x12, unsigned int)
#define PWM_LASER_SET_DUTY _IOW('A', 0x13, unsigned int)
#define PWM_LASER_GET_DUTY _IOR('A', 0x14, unsigned int)
#define PWM_LASER_ENABLE _IO('A', 0x15)
#define PWM_LASER_DISABLE _IO('A', 0x16)
int g_pwm_period = 1;
int g_pwm_duty = 50;
void print_help(void)
{
printf("usage:\n");
printf(" ./xxx period duty\n");
printf(" period ms, duty 0~100\n");
}
int main(int argc,char **argv)
{
unsigned int i = 0;
int dir = 0;
int duty, period;
int fd;
int count = 0;
if ((argc != 1) && (argc != 3)) {
print_help();
return -1;
}
if (argc == 3) {
g_pwm_period = atoi(argv[1]);
g_pwm_duty = atoi(argv[2]);
}
printf("Test period = %d duty = %d\n", g_pwm_period, g_pwm_duty);
fd = open("/dev/lcd-pwm-backlight", O_RDWR);
if (fd < 0) {
perror("open lcd-pwm-backlight fail");
exit(1);
}
ioctl(fd, PWM_LASER_DISABLE);
ioctl(fd, PWM_LASER_GET_PERIOD, &period);
printf("pwm period before = %d\n", period);
ioctl(fd, PWM_LASER_SET_PERIOD, &g_pwm_period);
ioctl(fd, PWM_LASER_GET_PERIOD, &period);
printf("pwm period after = %d\n", period);
ioctl(fd, PWM_LASER_GET_DUTY, &duty);
printf("pwm duty before = %d\n", duty);
ioctl(fd, PWM_LASER_SET_DUTY, &g_pwm_duty);
ioctl(fd, PWM_LASER_GET_DUTY, &duty);
printf("pwm duty after = %d\n", duty);
ioctl(fd, PWM_LASER_ENABLE);
while (1) {
sleep(1);
count++;
if ((count % 10) == 0) {
printf("count=%d\n", count);
}
}
close(fd);
return 0;
}
【底层调试】
1、pwmchipN,N是PWM芯片的基础
:/ # ls -l /sys/class/pwm/pwmchip4
lrwxrwxrwx 1 root root 0 2009-01-01 00:01 /sys/class/pwm/pwmchip4 -> ../../devices/platform/soc/ff800000.aobus/ff802000.pwm/pwm/pwmchip4
:/ # ls -l /sys/class/pwm/pwmchip0 ---》对应pwm_ef模块
lrwxrwxrwx 1 root root 0 2009-01-01 00:01 /sys/class/pwm/pwmchip0 -> ../../devices/platform/soc/ffd00000.cbus/ffd19000.pwm/pwm/pwmchip0
:/ #
2、检查pin脚复用状态
:/sys/kernel/debug/pinctrl # cat /sys/kernel/debug/pinctrl/pinctrl@ff634480/pinmux-pins
Pinmux settings per pin
Format: pin (name): mux_owner gpio_owner hog?
...
pin 22 (GPIOH_5): backlight (GPIO UNCLAIMED) function pwm_f group pwm_f_h
...
:/sys/kernel/debug/pinctrl # cat /sys/kernel/debug/pinctrl/pinctrl@ff634480/pinconf-pins
Pin config settings per pin
Format: pin (name): configs
...
pin 22 (GPIOH_5): input bias pull down, output drive strength (1), pin output (1 level)
...
ernel/debug/pinctrl/pinctrl@ff634480/pinmux-functions <
...
function: pwm_f, groups = [ pwm_f_x pwm_f_h pwm_f_z pwm_f_a11 ]
...
3、用户态修改PWM占空比
:/data # ./pwm_test 1 60 &
[1] 4249
:/data # Test period = 1 duty = 100
pwm period before = 1
pwm period after = 1
pwm duty before = 75
pwm duty after = 100
【应用控制】
1、添加pwm led背光驱动 驱动文件:common\drivers\leds\leds-pwm.c 配置文档:Documentation\devicetree\bindings\leds\leds-pwm.txt
确认内核有加入leds-pwm.c驱动 common/arch/arm64/configs/meson64_defconfig
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_PWM=y ---》确保为y
CONFIG_LEDS_TRIGGERS=y
2、增加设备树Dts节点 common/arch/arm64/boot/dts/amlogic/fj-a2-n1904.dts
pwmleds {
compatible = "pwm-leds";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pwm_f_pins2>;
sys {
active-low;
label = "lcd-backlight";
max-brightness = <255>;
pwms = <&pwm_ef MESON_PWM_1 50000 0>;
};
};
【应用调试】
1、通过文件系统调节亮度
/ # cd /sys/class/leds/lcd-backlight
/sys/class/leds/lcd-backlight # ---》设置不同的亮度值
echo 0 > brightness
echo 100 > brightness
echo 200 > brightness
echo 255 > brightness
2、在系统界面上调节亮度调节进度条 To-do
【参考资料】
1、pwm设备驱动调试 https://www.cnblogs.com/lialong1st/p/11436190.html
2、android背光设置 https://blog.csdn.net/izhetu/article/details/51096697
3、Linux驱动分析——pwm-leds https://blog.csdn.net/fang_yang_wa/article/details/112910107
|