海思3559A pwm驱动编写记录

由于hisi3559a的内核中未包含pwm驱动,故需自己编写。

1. 修改Kconfig文件

        打开XXX/drivers/pwm文件夹(XXX代表内核路径)中的Kconfig文件 增加如下内容

        config PWM_HISI

                tristate "hisi3559a PWM support by zd zjh"

                help

                       Generic PWM framework driver for hisi3559a by zd zjh.

        这样在menuconfig中将增加对应的选项

2. 修改makefile

        打开XXX/drivers/pwm文件夹(XXX代表内核路径)中的Makefile文件

        增加如下内容

        obj-$(CONFIG_PWM_HISI)         += pwm-hisi.o

        即根据menuconfig中是否选择PWM_HISI来决定是否编译我们将要添加的驱动程序。

3. 编写pwm驱动

        在XXX/drivers/pwm文件夹中新增pwm-hisi.c文件

        编写内容为:

/*
 * Driver for hisi3559a Pulse Width Modulation Controller
 *
 * Copyright (C) 2021 zjh <532679657@qq.com>
 *
 * Licensed under GPLv2.
 */

#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>

// 寄存器偏移量
#define PWM_CFG0          	0x00
#define PWM_CFG1		0x04
#define PWM_CFG2		0x08
#define PWM_CTRL		0x0C
#define PWM_STATE0		0x10
#define PWM_STATE1		0x14
#define PWM_STATE2		0x18
#define PWM_EN			BIT(0)
#define PWM_INV			BIT(1)
#define PWM_KEEP		BIT(2)


struct hisi_pwm_data {
	//bool has_prescaler_bypass;
	//bool has_rdy;
	unsigned int npwm;
};

struct hisi_pwm_chip {
	struct pwm_chip chip;
	struct clk *clk;
	void __iomem *base;
	spinlock_t ctrl_lock;
	const struct hisi_pwm_data *data;
};

static inline struct hisi_pwm_chip *to_hisi_pwm_chip(struct pwm_chip *chip)
{
	return container_of(chip, struct hisi_pwm_chip, chip);
}

// 读寄存器
static inline u32 hisi_pwm_readl(struct hisi_pwm_chip *chip,
				  unsigned long offset)
{
	return readl(chip->base + offset);
}
// 写寄存器
static inline void hisi_pwm_writel(struct hisi_pwm_chip *chip,
				    u32 val, unsigned long offset)
{
	writel(val, chip->base + offset);
}

// 配置pwm
static int hisi_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
			    int duty_ns, int period_ns)
{
	struct hisi_pwm_chip *hisi_pwm = to_hisi_pwm_chip(chip);
	u32 val = 0;
	u64 clk_rate = 0;
	int err;

	clk_rate = clk_get_rate(hisi_pwm->clk);
	printk(KERN_CRIT "pwm clk rate: %lld\n", clk_rate);
	
	err = clk_prepare_enable(hisi_pwm->clk);
	if (err) {
		dev_err(chip->dev, "failed to enable PWM clock\n");
		return err;
	}

	spin_lock(&hisi_pwm->ctrl_lock);

	//val = hisi_pwm_readl(hisi_pwm, PWM_CTRL);
	//val &= 0xFFFE;
	//hisi_pwm_writel(hisi_pwm, val, PWM_CTRL);

	//val = hisi_pwm_readl(hisi_pwm, PWM_CTRL);
	//val &= 0xFFFB;
	//hisi_pwm_writel(hisi_pwm, val, PWM_CTRL);

	/*do 
	{
		val = hisi_pwm_readl(hisi_pwm, PWM_STATE2);
		printk(KERN_CRIT "wait for pwm idle...\n");
	}
	while(0x400 == (val & 0x400));*/

	if (duty_ns >= 1)
	{
		hisi_pwm_writel(hisi_pwm, duty_ns, PWM_CFG1);
		printk(KERN_CRIT "duty reg have be written...\n");
	}

	if (period_ns >= 2)
	{
		hisi_pwm_writel(hisi_pwm, period_ns, PWM_CFG0);
		printk(KERN_CRIT "period reg have be written...\n");
	}

	//hisi_pwm_writel(hisi_pwm, 0xa, PWM_CFG2);
	printk(KERN_CRIT "num reg have be written...\n");

	//val = hisi_pwm_readl(hisi_pwm, PWM_CTRL);
	//val &= 0xFFFE;
	//hisi_pwm_writel(hisi_pwm, val, PWM_CTRL);

	val = hisi_pwm_readl(hisi_pwm, PWM_CTRL);
	val |= 0x05;
	hisi_pwm_writel(hisi_pwm, val, PWM_CTRL);

	spin_unlock(&hisi_pwm->ctrl_lock);
	clk_disable_unprepare(hisi_pwm->clk);

	return 0;
}

static int hisi_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
				  enum pwm_polarity polarity)
{
	struct hisi_pwm_chip *hisi_pwm = to_hisi_pwm_chip(chip);
	u32 val;
	int ret;

	ret = clk_prepare_enable(hisi_pwm->clk);
	if (ret) {
		dev_err(chip->dev, "failed to enable PWM clock\n");
		return ret;
	}

	spin_lock(&hisi_pwm->ctrl_lock);

	val = hisi_pwm_readl(hisi_pwm, PWM_CTRL);

	if (polarity != PWM_POLARITY_NORMAL)
		val |= 0x02;
	else
		val &= 0xFFFD;

	hisi_pwm_writel(hisi_pwm, val, PWM_CTRL);

	spin_unlock(&hisi_pwm->ctrl_lock);
	clk_disable_unprepare(hisi_pwm->clk);

	return 0;
}

static int hisi_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct hisi_pwm_chip *hisi_pwm = to_hisi_pwm_chip(chip);
	u32 val;
	int ret;

	ret = clk_prepare_enable(hisi_pwm->clk);
	if (ret) {
		dev_err(chip->dev, "failed to enable PWM clock\n");
		return ret;
	}

	spin_lock(&hisi_pwm->ctrl_lock);

	val = hisi_pwm_readl(hisi_pwm, PWM_CTRL);
	val |= 0x05;
	hisi_pwm_writel(hisi_pwm, val, PWM_CTRL);

	spin_unlock(&hisi_pwm->ctrl_lock);

	return 0;
}

static void hisi_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct hisi_pwm_chip *hisi_pwm = to_hisi_pwm_chip(chip);
	u32 val;

	spin_lock(&hisi_pwm->ctrl_lock);

	val = hisi_pwm_readl(hisi_pwm, PWM_CTRL);
	val &= 0xFFFE;
	hisi_pwm_writel(hisi_pwm, val, PWM_CTRL);

	spin_unlock(&hisi_pwm->ctrl_lock);

	clk_disable_unprepare(hisi_pwm->clk);
}

static const struct pwm_ops hisi_pwm_ops = {
	.config = hisi_pwm_config,
	.set_polarity = hisi_pwm_set_polarity,
	.enable = hisi_pwm_enable,
	.disable = hisi_pwm_disable,
	.owner = THIS_MODULE,
};

static const struct hisi_pwm_data hisi_pwm_data_I = {
	//.has_prescaler_bypass = true,
	//.has_rdy = true,
	.npwm = 1,
};

static const struct of_device_id hisi_pwm_of_match[] = {
	{ .compatible = "hisilicon,hi-pwm", .data = &hisi_pwm_data_I },
	{ }
};
MODULE_DEVICE_TABLE(of, hisi_pwm_of_match);

static int hisi_pwm_probe(struct platform_device *pdev)
{
	struct hisi_pwm_chip *pwm;
	struct resource *res;
	int ret;
	const struct of_device_id *match;

	match = of_match_device(hisi_pwm_of_match, &pdev->dev);

	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
	if (!pwm)
		return -ENOMEM;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	pwm->base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(pwm->base))
		return PTR_ERR(pwm->base);

	pwm->clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(pwm->clk))
		return PTR_ERR(pwm->clk);

	pwm->data = match->data;
	pwm->chip.dev = &pdev->dev;
	pwm->chip.ops = &hisi_pwm_ops;
	pwm->chip.base = -1;
	pwm->chip.npwm = pwm->data->npwm;
	pwm->chip.can_sleep = true;
	//pwm->chip.of_xlate = of_pwm_xlate_with_flags;
	//pwm->chip.of_pwm_n_cells = 3;

	spin_lock_init(&pwm->ctrl_lock);

	ret = pwmchip_add(&pwm->chip);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
		return ret;
	}

	platform_set_drvdata(pdev, pwm);

	return 0;

//clk_error:
//	pwmchip_remove(&pwm->chip);
//	return ret;
}

static int hisi_pwm_remove(struct platform_device *pdev)
{
	struct hisi_pwm_chip *pwm = platform_get_drvdata(pdev);

	return pwmchip_remove(&pwm->chip);
}

static struct platform_driver hisi_pwm_driver = {
	.driver = {
		.name = "hisi-pwm",
		.of_match_table = hisi_pwm_of_match,
	},
	.probe = hisi_pwm_probe,
	.remove = hisi_pwm_remove,
};
module_platform_driver(hisi_pwm_driver);// 宏展开为驱动入口

MODULE_ALIAS("platform:hisi-pwm");
MODULE_AUTHOR("zjh <532679657@qq.com>");
MODULE_DESCRIPTION("zd hisi3559a PWM driver");
MODULE_LICENSE("GPL v2");

4. 时钟驱动修改

        在hi3559av100-clock.h最后增加如下定义 

#define HI3559AV100_SHUB_PWM0_CLK		50 // add by zjh
#define HI3559AV100_SHUB_PWM1_CLK		51 // add by zjh
#define HI3559AV100_SHUB_PWM2_CLK		52 // add by zjh
#define HI3559AV100_SHUB_PWM3_CLK		53 // add by zjh
#define HI3559AV100_SHUB_PWM6_CLK		54 // add by zjh
#define HI3559AV100_SHUB_PWM7_CLK		55 // add by zjh

        修改clk-hi3559av100.c文件内容//add by zjh为修改内容 

/* shub div clk */
struct clk_div_table shub_spi_clk_table[] = {{0, 8},{1, 4},{2, 2}};
struct clk_div_table shub_spi4_clk_table[] = {{0, 8},{1, 4},{2, 2},{3, 1}};
struct clk_div_table shub_uart_div_clk_table[] = {{1, 8},{2, 4}};
struct clk_div_table shub_pwm_clk_table[] = {{0, 64},{1, 8},{2, 4},{3, 2},{4, 1}};//add by zjh

struct hisi_divider_clock hi3559av100_shub_div_clks[] __initdata = {
    { HI3559AV100_SHUB_SPI_SOURCE_CLK, "clk_spi_clk", "shub_clk", 0, 0x20, 24, 2, CLK_DIVIDER_ALLOW_ZERO, shub_spi_clk_table, },
    { HI3559AV100_SHUB_UART_DIV_CLK, "clk_uart_div_clk", "shub_clk", 0, 0x1c, 28, 2, CLK_DIVIDER_ALLOW_ZERO, shub_uart_div_clk_table, },
    { HI3559AV100_SHUB_PWM_SOURCE_CLK, "clk_pwm_clk", "shub_clk", 0, 0x18, 8, 3, CLK_DIVIDER_ALLOW_ZERO, shub_pwm_clk_table, },// add by zjh
};
/* shub gate clk */
static struct hisi_gate_clock hi3559av100_shub_gate_clks[] __initdata = {
	{ HI3559AV100_SHUB_SPI0_CLK , "clk_shub_spi0", "clk_spi_clk",
		0, 0x20, 1, 0, },
	{ HI3559AV100_SHUB_SPI1_CLK , "clk_shub_spi1", "clk_spi_clk",
		0, 0x20, 5, 0, },
	{ HI3559AV100_SHUB_SPI2_CLK , "clk_shub_spi2", "clk_spi_clk",
		0, 0x20, 9, 0, },

	{ HI3559AV100_SHUB_UART0_CLK, "clk_shub_uart0", "shub_uart_source_clk",
		0, 0x1c, 1, 0, },
	{ HI3559AV100_SHUB_UART1_CLK, "clk_shub_uart1", "shub_uart_source_clk",
		0, 0x1c, 5, 0, },
	{ HI3559AV100_SHUB_UART2_CLK, "clk_shub_uart2", "shub_uart_source_clk",
		0, 0x1c, 9, 0, },
	{ HI3559AV100_SHUB_UART3_CLK, "clk_shub_uart3", "shub_uart_source_clk",
		0, 0x1c, 13, 0, },
	{ HI3559AV100_SHUB_UART4_CLK, "clk_shub_uart4", "shub_uart_source_clk",
		0, 0x1c, 17, 0, },
	{ HI3559AV100_SHUB_UART5_CLK, "clk_shub_uart5", "shub_uart_source_clk",
		0, 0x1c, 21, 0, },
	{ HI3559AV100_SHUB_UART6_CLK, "clk_shub_uart6", "shub_uart_source_clk",
		0, 0x1c, 25, 0, },
	{ HI3559AV100_SHUB_PWM3_CLK, "clk_shub_pwm3", "clk_pwm_clk",// add by zjh
		0, 0x18, 4, 0, },
	{ HI3559AV100_SHUB_PWM6_CLK, "clk_shub_pwm6", "clk_pwm_clk",// add by zjh
		0, 0x18, 4, 0, },
	{ HI3559AV100_SHUB_PWM7_CLK, "clk_shub_pwm7", "clk_pwm_clk",// add by zjh
		0, 0x18, 4, 0, },
};

         海思未实现的驱动,通常在时钟配置这里也没有配置,所以有新增加外设驱动一定要检查时钟部分是否有。

5. 设备树修改

        增加如下内容

pwm0: pwm@0x18090060 {
		compatible = "hisilicon,hi-pwm";
		reg = <0x18090060 0x1c>;
		clocks = <&clock HI3559AV100_SHUB_PWM3_CLK>;
		#pwm-cells = <3>;
                clock-names = "apb_pclk";
                status = "disabled";
	};
	pwm1: pwm@0x180900C0 {
		compatible = "hisilicon,hi-pwm";
		reg = <0x180900C0 0x1c>;
		clocks = <&clock HI3559AV100_SHUB_PWM6_CLK>;
		#pwm-cells = <3>;
                clock-names = "apb_pclk";
                status = "disabled";
	};
	pwm2: pwm@0x180900E0 {
		compatible = "hisilicon,hi-pwm";
		reg = <0x180900E0 0x1c>;
		clocks = <&clock HI3559AV100_SHUB_PWM7_CLK>;
		#pwm-cells = <3>;
                clock-names = "apb_pclk";
                status = "disabled";
	};
	可以看到,上面的clocks项和时钟驱动中修改的内容是相关联的。

6. 应用层使用示例(Qt)

1.	#include <stdio.h>
2.	#include <fcntl.h>
3.	#include <sys/ioctl.h>
4.	#include <unistd.h>
5.	#include "QDebug"
6.	#include <string>
7.	
8.	int exportPwm(unsigned int pwm)
9.	{
10.	    int fd = -1;
11.	    std::string sPath;
12.	
13.	    if (pwm < 3)
14.	    {
15.	        sPath = "/sys/class/pwm/pwmchip"  + std::to_string(pwm) + "/export";
16.	
17.	        fd = open(sPath.c_str(), O_WRONLY);
18.	        if (fd < 0)
19.	        {
20.	            printf("open export failed\n");
21.	            return -1;
22.	        }
23.	
24.	        write(fd, "0", 2);
25.	        close(fd);
26.	        return 0;
27.	    }else
28.	        return -1;
29.	
30.	}
31.	
32.	int configPwm(unsigned int pwm, unsigned int period, unsigned int duty_cycle)
33.	{
34.	    int fd = 0, len_p = 0, len_d = 0;
35.	    char buf_p[32];
36.	    char buf_d[32];
37.	    std::string sPath;
38.	
39.	    len_p = snprintf(buf_p, sizeof(buf_p), "%d", period);
40.	    len_d = snprintf(buf_d, sizeof(buf_d), "%d", duty_cycle);
41.	
42.	    if (pwm < 3)
43.	    {
44.	        sPath = "/sys/class/pwm/pwmchip"  + std::to_string(pwm) + "/pwm0/period";
45.	
46.	        fd = open(sPath.c_str(), O_WRONLY);
47.	        if (fd < 0)
48.	        {
49.	            printf("open period failed\n");
50.	            return -1;
51.	        }
52.	        write(fd, buf_p, len_p);
53.	        close(fd);
54.	
55.	        sPath = "/sys/class/pwm/pwmchip"  + std::to_string(pwm) + "/pwm0/duty_cycle";
56.	        fd = open(sPath.c_str(), O_WRONLY);
57.	        if (fd < 0)
58.	        {
59.	            printf("open duty_cycle failed\n");
60.	            return -1;
61.	        }
62.	        write(fd, buf_d, len_d);
63.	        close(fd);
64.	        return 0;
65.	    }else
66.	        return -1;
67.	
68.	}
69.	
70.	int enablePwm(unsigned int pwm)
71.	{
72.	    int fd = 0;
73.	    std::string sPath;
74.	
75.	    if (pwm < 3)
76.	    {
77.	        sPath = "/sys/class/pwm/pwmchip"  + std::to_string(pwm) + "/pwm0/enable";
78.	
79.	        fd = open(sPath.c_str(), O_WRONLY);
80.	        if (fd < 0)
81.	        {
82.	            printf("open enable failed\n");
83.	            return -1;
84.	        }
85.	        write(fd, "1", 2);
86.	        close(fd);
87.	        return 0;
88.	    }else
89.	        return -1;
90.	
91.	}
92.	
93.	int disablePwm(unsigned int pwm)
94.	{
95.	    int fd = 0;
96.	    std::string sPath;
97.	
98.	    if (pwm < 3)
99.	    {
100.	        sPath = "/sys/class/pwm/pwmchip"  + std::to_string(pwm) + "/pwm0/enable";
101.	
102.	        fd = open(sPath.c_str(), O_WRONLY);
103.	        if (fd < 0)
104.	        {
105.	            printf("open enable failed\n");
106.	            return -1;
107.	        }
108.	        write(fd, "0", 2);
109.	        close(fd);
110.	        return 0;
111.	    }else
112.	        return -1;
113.	}
114.	
115.	void setDefalutLumia(void)
116.	{
117.	    int i = exportPwm(0);
118.	    if (i != 0)
119.	        qDebug() << "pwm export failed!";
120.	
121.	    configPwm(0, 750, 375);
122.	    enablePwm(0);
123.	}
124.	
125.	void initFanPWM(void)
126.	{
127.	    int i = exportPwm(1);
128.	    if (i != 0)
129.	        qDebug() << "pwm1 export failed!";
130.	
131.	    i = exportPwm(2);
132.	    if (i != 0)
133.	        qDebug() << "pwm2 export failed!";
134.	}

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值