由于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 <532679[email protected]>
*
* 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 <[email protected]>");
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: [email protected] {
compatible = "hisilicon,hi-pwm";
reg = <0x18090060 0x1c>;
clocks = <&clock HI3559AV100_SHUB_PWM3_CLK>;
#pwm-cells = <3>;
clock-names = "apb_pclk";
status = "disabled";
};
pwm1: [email protected] {
compatible = "hisilicon,hi-pwm";
reg = <0x180900C0 0x1c>;
clocks = <&clock HI3559AV100_SHUB_PWM6_CLK>;
#pwm-cells = <3>;
clock-names = "apb_pclk";
status = "disabled";
};
pwm2: [email protected] {
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. }