1 /* 2 * Mediatek Pulse Width Modulator driver 3 * 4 * Copyright (C) 2015 John Crispin <blogic@openwrt.org> 5 * Copyright (C) 2017 Zhi Mao <zhi.mao@mediatek.com> 6 * 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> 19 #include <linux/of_device.h> 20 #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 32 #define PWM45DWIDTH_FIXUP 0x30 33 #define PWMTHRES 0x30 34 #define PWM45THRES_FIXUP 0x34 35 36 #define PWM_CLK_DIV_MAX 7 37 38 enum { 39 MTK_CLK_MAIN = 0, 40 MTK_CLK_TOP, 41 MTK_CLK_PWM1, 42 MTK_CLK_PWM2, 43 MTK_CLK_PWM3, 44 MTK_CLK_PWM4, 45 MTK_CLK_PWM5, 46 MTK_CLK_PWM6, 47 MTK_CLK_PWM7, 48 MTK_CLK_PWM8, 49 MTK_CLK_MAX, 50 }; 51 52 static const char * const mtk_pwm_clk_name[MTK_CLK_MAX] = { 53 "main", "top", "pwm1", "pwm2", "pwm3", "pwm4", "pwm5", "pwm6", "pwm7", 54 "pwm8" 55 }; 56 57 struct mtk_pwm_platform_data { 58 unsigned int num_pwms; 59 bool pwm45_fixup; 60 }; 61 62 /** 63 * struct mtk_pwm_chip - struct representing PWM chip 64 * @chip: linux PWM chip representation 65 * @regs: base address of PWM chip 66 * @clks: list of clocks 67 */ 68 struct mtk_pwm_chip { 69 struct pwm_chip chip; 70 void __iomem *regs; 71 struct clk *clks[MTK_CLK_MAX]; 72 const struct mtk_pwm_platform_data *soc; 73 }; 74 75 static const unsigned int mtk_pwm_reg_offset[] = { 76 0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220 77 }; 78 79 static inline struct mtk_pwm_chip *to_mtk_pwm_chip(struct pwm_chip *chip) 80 { 81 return container_of(chip, struct mtk_pwm_chip, chip); 82 } 83 84 static int mtk_pwm_clk_enable(struct pwm_chip *chip, struct pwm_device *pwm) 85 { 86 struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip); 87 int ret; 88 89 ret = clk_prepare_enable(pc->clks[MTK_CLK_TOP]); 90 if (ret < 0) 91 return ret; 92 93 ret = clk_prepare_enable(pc->clks[MTK_CLK_MAIN]); 94 if (ret < 0) 95 goto disable_clk_top; 96 97 ret = clk_prepare_enable(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]); 98 if (ret < 0) 99 goto disable_clk_main; 100 101 return 0; 102 103 disable_clk_main: 104 clk_disable_unprepare(pc->clks[MTK_CLK_MAIN]); 105 disable_clk_top: 106 clk_disable_unprepare(pc->clks[MTK_CLK_TOP]); 107 108 return ret; 109 } 110 111 static void mtk_pwm_clk_disable(struct pwm_chip *chip, struct pwm_device *pwm) 112 { 113 struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip); 114 115 clk_disable_unprepare(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]); 116 clk_disable_unprepare(pc->clks[MTK_CLK_MAIN]); 117 clk_disable_unprepare(pc->clks[MTK_CLK_TOP]); 118 } 119 120 static inline u32 mtk_pwm_readl(struct mtk_pwm_chip *chip, unsigned int num, 121 unsigned int offset) 122 { 123 return readl(chip->regs + mtk_pwm_reg_offset[num] + offset); 124 } 125 126 static inline void mtk_pwm_writel(struct mtk_pwm_chip *chip, 127 unsigned int num, unsigned int offset, 128 u32 value) 129 { 130 writel(value, chip->regs + mtk_pwm_reg_offset[num] + offset); 131 } 132 133 static int mtk_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 134 int duty_ns, int period_ns) 135 { 136 struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip); 137 struct clk *clk = pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]; 138 u32 clkdiv = 0, cnt_period, cnt_duty, reg_width = PWMDWIDTH, 139 reg_thres = PWMTHRES; 140 u64 resolution; 141 int ret; 142 143 ret = mtk_pwm_clk_enable(chip, pwm); 144 if (ret < 0) 145 return ret; 146 147 /* Using resolution in picosecond gets accuracy higher */ 148 resolution = (u64)NSEC_PER_SEC * 1000; 149 do_div(resolution, clk_get_rate(clk)); 150 151 cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution); 152 while (cnt_period > 8191) { 153 resolution *= 2; 154 clkdiv++; 155 cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, 156 resolution); 157 } 158 159 if (clkdiv > PWM_CLK_DIV_MAX) { 160 mtk_pwm_clk_disable(chip, pwm); 161 dev_err(chip->dev, "period %d not supported\n", period_ns); 162 return -EINVAL; 163 } 164 165 if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) { 166 /* 167 * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES 168 * from the other PWMs on MT7623. 169 */ 170 reg_width = PWM45DWIDTH_FIXUP; 171 reg_thres = PWM45THRES_FIXUP; 172 } 173 174 cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution); 175 mtk_pwm_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv); 176 mtk_pwm_writel(pc, pwm->hwpwm, reg_width, cnt_period); 177 mtk_pwm_writel(pc, pwm->hwpwm, reg_thres, cnt_duty); 178 179 mtk_pwm_clk_disable(chip, pwm); 180 181 return 0; 182 } 183 184 static int mtk_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 185 { 186 struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip); 187 u32 value; 188 int ret; 189 190 ret = mtk_pwm_clk_enable(chip, pwm); 191 if (ret < 0) 192 return ret; 193 194 value = readl(pc->regs); 195 value |= BIT(pwm->hwpwm); 196 writel(value, pc->regs); 197 198 return 0; 199 } 200 201 static void mtk_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 202 { 203 struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip); 204 u32 value; 205 206 value = readl(pc->regs); 207 value &= ~BIT(pwm->hwpwm); 208 writel(value, pc->regs); 209 210 mtk_pwm_clk_disable(chip, pwm); 211 } 212 213 static const struct pwm_ops mtk_pwm_ops = { 214 .config = mtk_pwm_config, 215 .enable = mtk_pwm_enable, 216 .disable = mtk_pwm_disable, 217 .owner = THIS_MODULE, 218 }; 219 220 static int mtk_pwm_probe(struct platform_device *pdev) 221 { 222 const struct mtk_pwm_platform_data *data; 223 struct mtk_pwm_chip *pc; 224 struct resource *res; 225 unsigned int i; 226 int ret; 227 228 pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); 229 if (!pc) 230 return -ENOMEM; 231 232 data = of_device_get_match_data(&pdev->dev); 233 if (data == NULL) 234 return -EINVAL; 235 pc->soc = data; 236 237 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 238 pc->regs = devm_ioremap_resource(&pdev->dev, res); 239 if (IS_ERR(pc->regs)) 240 return PTR_ERR(pc->regs); 241 242 for (i = 0; i < data->num_pwms + 2; i++) { 243 pc->clks[i] = devm_clk_get(&pdev->dev, mtk_pwm_clk_name[i]); 244 if (IS_ERR(pc->clks[i])) { 245 dev_err(&pdev->dev, "clock: %s fail: %ld\n", 246 mtk_pwm_clk_name[i], PTR_ERR(pc->clks[i])); 247 return PTR_ERR(pc->clks[i]); 248 } 249 } 250 251 platform_set_drvdata(pdev, pc); 252 253 pc->chip.dev = &pdev->dev; 254 pc->chip.ops = &mtk_pwm_ops; 255 pc->chip.base = -1; 256 pc->chip.npwm = data->num_pwms; 257 258 ret = pwmchip_add(&pc->chip); 259 if (ret < 0) { 260 dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); 261 return ret; 262 } 263 264 return 0; 265 } 266 267 static int mtk_pwm_remove(struct platform_device *pdev) 268 { 269 struct mtk_pwm_chip *pc = platform_get_drvdata(pdev); 270 271 return pwmchip_remove(&pc->chip); 272 } 273 274 static const struct mtk_pwm_platform_data mt2712_pwm_data = { 275 .num_pwms = 8, 276 .pwm45_fixup = false, 277 }; 278 279 static const struct mtk_pwm_platform_data mt7622_pwm_data = { 280 .num_pwms = 6, 281 .pwm45_fixup = false, 282 }; 283 284 static const struct mtk_pwm_platform_data mt7623_pwm_data = { 285 .num_pwms = 5, 286 .pwm45_fixup = true, 287 }; 288 289 static const struct of_device_id mtk_pwm_of_match[] = { 290 { .compatible = "mediatek,mt2712-pwm", .data = &mt2712_pwm_data }, 291 { .compatible = "mediatek,mt7622-pwm", .data = &mt7622_pwm_data }, 292 { .compatible = "mediatek,mt7623-pwm", .data = &mt7623_pwm_data }, 293 { }, 294 }; 295 MODULE_DEVICE_TABLE(of, mtk_pwm_of_match); 296 297 static struct platform_driver mtk_pwm_driver = { 298 .driver = { 299 .name = "mtk-pwm", 300 .of_match_table = mtk_pwm_of_match, 301 }, 302 .probe = mtk_pwm_probe, 303 .remove = mtk_pwm_remove, 304 }; 305 module_platform_driver(mtk_pwm_driver); 306 307 MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); 308 MODULE_LICENSE("GPL"); 309