blob: 6b9a5857b5b6cb159c655b62eaa4ca31174068c8 [file] [log] [blame]
John Crispincaf065f2017-01-23 19:34:37 +01001/*
2 * Mediatek Pulse Width Modulator driver
3 *
4 * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
Zhi Maoe7c197e2017-06-30 14:05:18 +08005 * Copyright (C) 2017 Zhi Mao <zhi.mao@mediatek.com>
John Crispincaf065f2017-01-23 19:34:37 +01006 *
7 * This file is licensed under the terms of the GNU General Public
8 * License version 2. This program is licensed "as is" without any
9 * warranty of any kind, whether express or implied.
10 */
11
12#include <linux/err.h>
13#include <linux/io.h>
14#include <linux/ioport.h>
15#include <linux/kernel.h>
16#include <linux/module.h>
17#include <linux/clk.h>
18#include <linux/of.h>
Zhi Mao424268c2017-10-25 18:11:01 +080019#include <linux/of_device.h>
John Crispincaf065f2017-01-23 19:34:37 +010020#include <linux/platform_device.h>
21#include <linux/pwm.h>
22#include <linux/slab.h>
23#include <linux/types.h>
24
25/* PWM registers and bits definitions */
26#define PWMCON 0x00
27#define PWMHDUR 0x04
28#define PWMLDUR 0x08
29#define PWMGDUR 0x0c
30#define PWMWAVENUM 0x28
31#define PWMDWIDTH 0x2c
Sean Wang360cc032018-03-01 16:19:12 +080032#define PWM45DWIDTH_FIXUP 0x30
John Crispincaf065f2017-01-23 19:34:37 +010033#define PWMTHRES 0x30
Sean Wang360cc032018-03-01 16:19:12 +080034#define PWM45THRES_FIXUP 0x34
John Crispincaf065f2017-01-23 19:34:37 +010035
Zhi Mao8bdb65d2017-06-30 14:05:20 +080036#define PWM_CLK_DIV_MAX 7
37
Sam Shih25037812019-09-20 06:49:05 +080038struct pwm_mediatek_of_data {
Zhi Mao424268c2017-10-25 18:11:01 +080039 unsigned int num_pwms;
Sean Wang360cc032018-03-01 16:19:12 +080040 bool pwm45_fixup;
John Crispincaf065f2017-01-23 19:34:37 +010041};
42
43/**
Sam Shih25037812019-09-20 06:49:05 +080044 * struct pwm_mediatek_chip - struct representing PWM chip
John Crispincaf065f2017-01-23 19:34:37 +010045 * @chip: linux PWM chip representation
46 * @regs: base address of PWM chip
Sam Shihefecdeb2019-09-20 06:49:04 +080047 * @clk_top: the top clock generator
48 * @clk_main: the clock used by PWM core
49 * @clk_pwms: the clock used by each PWM channel
50 * @clk_freq: the fix clock frequency of legacy MIPS SoC
John Crispincaf065f2017-01-23 19:34:37 +010051 */
Sam Shih25037812019-09-20 06:49:05 +080052struct pwm_mediatek_chip {
John Crispincaf065f2017-01-23 19:34:37 +010053 struct pwm_chip chip;
54 void __iomem *regs;
Sam Shihefecdeb2019-09-20 06:49:04 +080055 struct clk *clk_top;
56 struct clk *clk_main;
57 struct clk **clk_pwms;
Sam Shih25037812019-09-20 06:49:05 +080058 const struct pwm_mediatek_of_data *soc;
John Crispincaf065f2017-01-23 19:34:37 +010059};
60
Sam Shih25037812019-09-20 06:49:05 +080061static const unsigned int pwm_mediatek_reg_offset[] = {
Zhi Mao424268c2017-10-25 18:11:01 +080062 0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220
63};
64
Sam Shih25037812019-09-20 06:49:05 +080065static inline struct pwm_mediatek_chip *
66to_pwm_mediatek_chip(struct pwm_chip *chip)
John Crispincaf065f2017-01-23 19:34:37 +010067{
Sam Shih25037812019-09-20 06:49:05 +080068 return container_of(chip, struct pwm_mediatek_chip, chip);
John Crispincaf065f2017-01-23 19:34:37 +010069}
70
Sam Shih25037812019-09-20 06:49:05 +080071static int pwm_mediatek_clk_enable(struct pwm_chip *chip,
72 struct pwm_device *pwm)
Zhi Maoe7c197e2017-06-30 14:05:18 +080073{
Sam Shih25037812019-09-20 06:49:05 +080074 struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
Zhi Maoe7c197e2017-06-30 14:05:18 +080075 int ret;
76
Sam Shihefecdeb2019-09-20 06:49:04 +080077 ret = clk_prepare_enable(pc->clk_top);
Zhi Maoe7c197e2017-06-30 14:05:18 +080078 if (ret < 0)
79 return ret;
80
Sam Shihefecdeb2019-09-20 06:49:04 +080081 ret = clk_prepare_enable(pc->clk_main);
Zhi Maoe7c197e2017-06-30 14:05:18 +080082 if (ret < 0)
83 goto disable_clk_top;
84
Sam Shihefecdeb2019-09-20 06:49:04 +080085 ret = clk_prepare_enable(pc->clk_pwms[pwm->hwpwm]);
Zhi Maoe7c197e2017-06-30 14:05:18 +080086 if (ret < 0)
87 goto disable_clk_main;
88
89 return 0;
90
91disable_clk_main:
Sam Shihefecdeb2019-09-20 06:49:04 +080092 clk_disable_unprepare(pc->clk_main);
Zhi Maoe7c197e2017-06-30 14:05:18 +080093disable_clk_top:
Sam Shihefecdeb2019-09-20 06:49:04 +080094 clk_disable_unprepare(pc->clk_top);
Zhi Maoe7c197e2017-06-30 14:05:18 +080095
96 return ret;
97}
98
Sam Shih25037812019-09-20 06:49:05 +080099static void pwm_mediatek_clk_disable(struct pwm_chip *chip,
100 struct pwm_device *pwm)
Zhi Maoe7c197e2017-06-30 14:05:18 +0800101{
Sam Shih25037812019-09-20 06:49:05 +0800102 struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
Zhi Maoe7c197e2017-06-30 14:05:18 +0800103
Sam Shihefecdeb2019-09-20 06:49:04 +0800104 clk_disable_unprepare(pc->clk_pwms[pwm->hwpwm]);
105 clk_disable_unprepare(pc->clk_main);
106 clk_disable_unprepare(pc->clk_top);
Zhi Maoe7c197e2017-06-30 14:05:18 +0800107}
108
Sam Shih25037812019-09-20 06:49:05 +0800109static inline u32 pwm_mediatek_readl(struct pwm_mediatek_chip *chip,
110 unsigned int num, unsigned int offset)
John Crispincaf065f2017-01-23 19:34:37 +0100111{
Sam Shih25037812019-09-20 06:49:05 +0800112 return readl(chip->regs + pwm_mediatek_reg_offset[num] + offset);
John Crispincaf065f2017-01-23 19:34:37 +0100113}
114
Sam Shih25037812019-09-20 06:49:05 +0800115static inline void pwm_mediatek_writel(struct pwm_mediatek_chip *chip,
116 unsigned int num, unsigned int offset,
117 u32 value)
John Crispincaf065f2017-01-23 19:34:37 +0100118{
Sam Shih25037812019-09-20 06:49:05 +0800119 writel(value, chip->regs + pwm_mediatek_reg_offset[num] + offset);
John Crispincaf065f2017-01-23 19:34:37 +0100120}
121
Sam Shih25037812019-09-20 06:49:05 +0800122static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
123 int duty_ns, int period_ns)
John Crispincaf065f2017-01-23 19:34:37 +0100124{
Sam Shih25037812019-09-20 06:49:05 +0800125 struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
Sean Wang04c0a4e2018-03-02 16:49:14 +0800126 u32 clkdiv = 0, cnt_period, cnt_duty, reg_width = PWMDWIDTH,
Sean Wang360cc032018-03-01 16:19:12 +0800127 reg_thres = PWMTHRES;
Sean Wang04c0a4e2018-03-02 16:49:14 +0800128 u64 resolution;
Zhi Maoe7c197e2017-06-30 14:05:18 +0800129 int ret;
130
Sam Shih25037812019-09-20 06:49:05 +0800131 ret = pwm_mediatek_clk_enable(chip, pwm);
132
Zhi Maoe7c197e2017-06-30 14:05:18 +0800133 if (ret < 0)
134 return ret;
John Crispincaf065f2017-01-23 19:34:37 +0100135
Sean Wang04c0a4e2018-03-02 16:49:14 +0800136 /* Using resolution in picosecond gets accuracy higher */
137 resolution = (u64)NSEC_PER_SEC * 1000;
Sam Shih25037812019-09-20 06:49:05 +0800138 do_div(resolution, clk_get_rate(pc->clk_pwms[pwm->hwpwm]));
John Crispincaf065f2017-01-23 19:34:37 +0100139
Sean Wang04c0a4e2018-03-02 16:49:14 +0800140 cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution);
141 while (cnt_period > 8191) {
John Crispincaf065f2017-01-23 19:34:37 +0100142 resolution *= 2;
143 clkdiv++;
Sean Wang04c0a4e2018-03-02 16:49:14 +0800144 cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000,
145 resolution);
John Crispincaf065f2017-01-23 19:34:37 +0100146 }
147
Zhi Mao8bdb65d2017-06-30 14:05:20 +0800148 if (clkdiv > PWM_CLK_DIV_MAX) {
Sam Shih25037812019-09-20 06:49:05 +0800149 pwm_mediatek_clk_disable(chip, pwm);
Zhi Mao8bdb65d2017-06-30 14:05:20 +0800150 dev_err(chip->dev, "period %d not supported\n", period_ns);
John Crispincaf065f2017-01-23 19:34:37 +0100151 return -EINVAL;
Zhi Mao8bdb65d2017-06-30 14:05:20 +0800152 }
John Crispincaf065f2017-01-23 19:34:37 +0100153
Sean Wang360cc032018-03-01 16:19:12 +0800154 if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
155 /*
156 * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
157 * from the other PWMs on MT7623.
158 */
159 reg_width = PWM45DWIDTH_FIXUP;
160 reg_thres = PWM45THRES_FIXUP;
161 }
162
Sean Wang04c0a4e2018-03-02 16:49:14 +0800163 cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution);
Sam Shih25037812019-09-20 06:49:05 +0800164 pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv);
165 pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, cnt_period);
166 pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, cnt_duty);
John Crispincaf065f2017-01-23 19:34:37 +0100167
Sam Shih25037812019-09-20 06:49:05 +0800168 pwm_mediatek_clk_disable(chip, pwm);
Zhi Maoe7c197e2017-06-30 14:05:18 +0800169
John Crispincaf065f2017-01-23 19:34:37 +0100170 return 0;
171}
172
Sam Shih25037812019-09-20 06:49:05 +0800173static int pwm_mediatek_enable(struct pwm_chip *chip, struct pwm_device *pwm)
John Crispincaf065f2017-01-23 19:34:37 +0100174{
Sam Shih25037812019-09-20 06:49:05 +0800175 struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
John Crispincaf065f2017-01-23 19:34:37 +0100176 u32 value;
177 int ret;
178
Sam Shih25037812019-09-20 06:49:05 +0800179 ret = pwm_mediatek_clk_enable(chip, pwm);
John Crispincaf065f2017-01-23 19:34:37 +0100180 if (ret < 0)
181 return ret;
182
183 value = readl(pc->regs);
184 value |= BIT(pwm->hwpwm);
185 writel(value, pc->regs);
186
187 return 0;
188}
189
Sam Shih25037812019-09-20 06:49:05 +0800190static void pwm_mediatek_disable(struct pwm_chip *chip, struct pwm_device *pwm)
John Crispincaf065f2017-01-23 19:34:37 +0100191{
Sam Shih25037812019-09-20 06:49:05 +0800192 struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
John Crispincaf065f2017-01-23 19:34:37 +0100193 u32 value;
194
195 value = readl(pc->regs);
196 value &= ~BIT(pwm->hwpwm);
197 writel(value, pc->regs);
198
Sam Shih25037812019-09-20 06:49:05 +0800199 pwm_mediatek_clk_disable(chip, pwm);
John Crispincaf065f2017-01-23 19:34:37 +0100200}
201
Sam Shih25037812019-09-20 06:49:05 +0800202static const struct pwm_ops pwm_mediatek_ops = {
203 .config = pwm_mediatek_config,
204 .enable = pwm_mediatek_enable,
205 .disable = pwm_mediatek_disable,
John Crispincaf065f2017-01-23 19:34:37 +0100206 .owner = THIS_MODULE,
207};
208
Sam Shih25037812019-09-20 06:49:05 +0800209static int pwm_mediatek_probe(struct platform_device *pdev)
John Crispincaf065f2017-01-23 19:34:37 +0100210{
Sam Shih25037812019-09-20 06:49:05 +0800211 struct pwm_mediatek_chip *pc;
John Crispincaf065f2017-01-23 19:34:37 +0100212 struct resource *res;
213 unsigned int i;
214 int ret;
215
216 pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
217 if (!pc)
218 return -ENOMEM;
219
Sam Shihe6c7c252019-09-20 06:49:02 +0800220 pc->soc = of_device_get_match_data(&pdev->dev);
Zhi Mao424268c2017-10-25 18:11:01 +0800221
John Crispincaf065f2017-01-23 19:34:37 +0100222 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
223 pc->regs = devm_ioremap_resource(&pdev->dev, res);
224 if (IS_ERR(pc->regs))
225 return PTR_ERR(pc->regs);
226
Sam Shihefecdeb2019-09-20 06:49:04 +0800227 pc->clk_pwms = devm_kcalloc(&pdev->dev, pc->soc->num_pwms,
228 sizeof(*pc->clk_pwms), GFP_KERNEL);
229 if (!pc->clk_pwms)
230 return -ENOMEM;
231
232 pc->clk_top = devm_clk_get(&pdev->dev, "top");
233 if (IS_ERR(pc->clk_top)) {
234 dev_err(&pdev->dev, "clock: top fail: %ld\n",
235 PTR_ERR(pc->clk_top));
236 return PTR_ERR(pc->clk_top);
237 }
238
239 pc->clk_main = devm_clk_get(&pdev->dev, "main");
240 if (IS_ERR(pc->clk_main)) {
241 dev_err(&pdev->dev, "clock: main fail: %ld\n",
242 PTR_ERR(pc->clk_main));
243 return PTR_ERR(pc->clk_main);
244 }
245
246 for (i = 0; i < pc->soc->num_pwms; i++) {
247 char name[8];
248
249 snprintf(name, sizeof(name), "pwm%d", i + 1);
250
251 pc->clk_pwms[i] = devm_clk_get(&pdev->dev, name);
252 if (IS_ERR(pc->clk_pwms[i])) {
Zhi Mao424268c2017-10-25 18:11:01 +0800253 dev_err(&pdev->dev, "clock: %s fail: %ld\n",
Sam Shihefecdeb2019-09-20 06:49:04 +0800254 name, PTR_ERR(pc->clk_pwms[i]));
255 return PTR_ERR(pc->clk_pwms[i]);
Zhi Mao424268c2017-10-25 18:11:01 +0800256 }
John Crispincaf065f2017-01-23 19:34:37 +0100257 }
258
John Crispincaf065f2017-01-23 19:34:37 +0100259 platform_set_drvdata(pdev, pc);
260
261 pc->chip.dev = &pdev->dev;
Sam Shih25037812019-09-20 06:49:05 +0800262 pc->chip.ops = &pwm_mediatek_ops;
John Crispincaf065f2017-01-23 19:34:37 +0100263 pc->chip.base = -1;
Sam Shihe6c7c252019-09-20 06:49:02 +0800264 pc->chip.npwm = pc->soc->num_pwms;
John Crispincaf065f2017-01-23 19:34:37 +0100265
266 ret = pwmchip_add(&pc->chip);
267 if (ret < 0) {
268 dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
Zhi Maoe7c197e2017-06-30 14:05:18 +0800269 return ret;
John Crispincaf065f2017-01-23 19:34:37 +0100270 }
271
272 return 0;
John Crispincaf065f2017-01-23 19:34:37 +0100273}
274
Sam Shih25037812019-09-20 06:49:05 +0800275static int pwm_mediatek_remove(struct platform_device *pdev)
John Crispincaf065f2017-01-23 19:34:37 +0100276{
Sam Shih25037812019-09-20 06:49:05 +0800277 struct pwm_mediatek_chip *pc = platform_get_drvdata(pdev);
John Crispincaf065f2017-01-23 19:34:37 +0100278
279 return pwmchip_remove(&pc->chip);
280}
281
Sam Shih25037812019-09-20 06:49:05 +0800282static const struct pwm_mediatek_of_data mt2712_pwm_data = {
Zhi Mao424268c2017-10-25 18:11:01 +0800283 .num_pwms = 8,
Sean Wang360cc032018-03-01 16:19:12 +0800284 .pwm45_fixup = false,
Zhi Mao424268c2017-10-25 18:11:01 +0800285};
286
Sam Shih25037812019-09-20 06:49:05 +0800287static const struct pwm_mediatek_of_data mt7622_pwm_data = {
Zhi Mao424268c2017-10-25 18:11:01 +0800288 .num_pwms = 6,
Sean Wang360cc032018-03-01 16:19:12 +0800289 .pwm45_fixup = false,
Zhi Mao424268c2017-10-25 18:11:01 +0800290};
291
Sam Shih25037812019-09-20 06:49:05 +0800292static const struct pwm_mediatek_of_data mt7623_pwm_data = {
Zhi Mao424268c2017-10-25 18:11:01 +0800293 .num_pwms = 5,
Sean Wang360cc032018-03-01 16:19:12 +0800294 .pwm45_fixup = true,
John Crispin8cdc43a2018-07-25 11:52:09 +0200295};
296
Sam Shih25037812019-09-20 06:49:05 +0800297static const struct pwm_mediatek_of_data mt7628_pwm_data = {
John Crispin8cdc43a2018-07-25 11:52:09 +0200298 .num_pwms = 4,
299 .pwm45_fixup = true,
Zhi Mao424268c2017-10-25 18:11:01 +0800300};
301
Sam Shih25037812019-09-20 06:49:05 +0800302static const struct pwm_mediatek_of_data mt8516_pwm_data = {
Fabien Parent8d190722019-08-05 14:58:48 +0200303 .num_pwms = 5,
304 .pwm45_fixup = false,
Fabien Parent8d190722019-08-05 14:58:48 +0200305};
306
Sam Shih25037812019-09-20 06:49:05 +0800307static const struct of_device_id pwm_mediatek_of_match[] = {
Zhi Mao424268c2017-10-25 18:11:01 +0800308 { .compatible = "mediatek,mt2712-pwm", .data = &mt2712_pwm_data },
309 { .compatible = "mediatek,mt7622-pwm", .data = &mt7622_pwm_data },
310 { .compatible = "mediatek,mt7623-pwm", .data = &mt7623_pwm_data },
John Crispin8cdc43a2018-07-25 11:52:09 +0200311 { .compatible = "mediatek,mt7628-pwm", .data = &mt7628_pwm_data },
Fabien Parent8d190722019-08-05 14:58:48 +0200312 { .compatible = "mediatek,mt8516-pwm", .data = &mt8516_pwm_data },
Zhi Mao424268c2017-10-25 18:11:01 +0800313 { },
John Crispincaf065f2017-01-23 19:34:37 +0100314};
Sam Shih25037812019-09-20 06:49:05 +0800315MODULE_DEVICE_TABLE(of, pwm_mediatek_of_match);
John Crispincaf065f2017-01-23 19:34:37 +0100316
Sam Shih25037812019-09-20 06:49:05 +0800317static struct platform_driver pwm_mediatek_driver = {
John Crispincaf065f2017-01-23 19:34:37 +0100318 .driver = {
Sam Shih25037812019-09-20 06:49:05 +0800319 .name = "pwm-mediatek",
320 .of_match_table = pwm_mediatek_of_match,
John Crispincaf065f2017-01-23 19:34:37 +0100321 },
Sam Shih25037812019-09-20 06:49:05 +0800322 .probe = pwm_mediatek_probe,
323 .remove = pwm_mediatek_remove,
John Crispincaf065f2017-01-23 19:34:37 +0100324};
Sam Shih25037812019-09-20 06:49:05 +0800325module_platform_driver(pwm_mediatek_driver);
John Crispincaf065f2017-01-23 19:34:37 +0100326
327MODULE_AUTHOR("John Crispin <blogic@openwrt.org>");
John Crispincaf065f2017-01-23 19:34:37 +0100328MODULE_LICENSE("GPL");