1 /* 2 * MediaTek display pulse-width-modulation controller driver. 3 * Copyright (c) 2015 MediaTek Inc. 4 * Author: YH Huang <yh.huang@mediatek.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 */ 15 16 #include <linux/clk.h> 17 #include <linux/err.h> 18 #include <linux/io.h> 19 #include <linux/module.h> 20 #include <linux/of.h> 21 #include <linux/platform_device.h> 22 #include <linux/pwm.h> 23 #include <linux/slab.h> 24 25 #define DISP_PWM_EN 0x00 26 #define PWM_ENABLE_MASK BIT(0) 27 28 #define DISP_PWM_COMMIT 0x08 29 #define PWM_COMMIT_MASK BIT(0) 30 31 #define DISP_PWM_CON_0 0x10 32 #define PWM_CLKDIV_SHIFT 16 33 #define PWM_CLKDIV_MAX 0x3ff 34 #define PWM_CLKDIV_MASK (PWM_CLKDIV_MAX << PWM_CLKDIV_SHIFT) 35 36 #define DISP_PWM_CON_1 0x14 37 #define PWM_PERIOD_BIT_WIDTH 12 38 #define PWM_PERIOD_MASK ((1 << PWM_PERIOD_BIT_WIDTH) - 1) 39 40 #define PWM_HIGH_WIDTH_SHIFT 16 41 #define PWM_HIGH_WIDTH_MASK (0x1fff << PWM_HIGH_WIDTH_SHIFT) 42 43 struct mtk_disp_pwm { 44 struct pwm_chip chip; 45 struct clk *clk_main; 46 struct clk *clk_mm; 47 void __iomem *base; 48 }; 49 50 static inline struct mtk_disp_pwm *to_mtk_disp_pwm(struct pwm_chip *chip) 51 { 52 return container_of(chip, struct mtk_disp_pwm, chip); 53 } 54 55 static void mtk_disp_pwm_update_bits(struct mtk_disp_pwm *mdp, u32 offset, 56 u32 mask, u32 data) 57 { 58 void __iomem *address = mdp->base + offset; 59 u32 value; 60 61 value = readl(address); 62 value &= ~mask; 63 value |= data; 64 writel(value, address); 65 } 66 67 static int mtk_disp_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 68 int duty_ns, int period_ns) 69 { 70 struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); 71 u32 clk_div, period, high_width, value; 72 u64 div, rate; 73 int err; 74 75 /* 76 * Find period, high_width and clk_div to suit duty_ns and period_ns. 77 * Calculate proper div value to keep period value in the bound. 78 * 79 * period_ns = 10^9 * (clk_div + 1) * (period + 1) / PWM_CLK_RATE 80 * duty_ns = 10^9 * (clk_div + 1) * high_width / PWM_CLK_RATE 81 * 82 * period = (PWM_CLK_RATE * period_ns) / (10^9 * (clk_div + 1)) - 1 83 * high_width = (PWM_CLK_RATE * duty_ns) / (10^9 * (clk_div + 1)) 84 */ 85 rate = clk_get_rate(mdp->clk_main); 86 clk_div = div_u64(rate * period_ns, NSEC_PER_SEC) >> 87 PWM_PERIOD_BIT_WIDTH; 88 if (clk_div > PWM_CLKDIV_MAX) 89 return -EINVAL; 90 91 div = NSEC_PER_SEC * (clk_div + 1); 92 period = div64_u64(rate * period_ns, div); 93 if (period > 0) 94 period--; 95 96 high_width = div64_u64(rate * duty_ns, div); 97 value = period | (high_width << PWM_HIGH_WIDTH_SHIFT); 98 99 err = clk_enable(mdp->clk_main); 100 if (err < 0) 101 return err; 102 103 err = clk_enable(mdp->clk_mm); 104 if (err < 0) { 105 clk_disable(mdp->clk_main); 106 return err; 107 } 108 109 mtk_disp_pwm_update_bits(mdp, DISP_PWM_CON_0, PWM_CLKDIV_MASK, 110 clk_div << PWM_CLKDIV_SHIFT); 111 mtk_disp_pwm_update_bits(mdp, DISP_PWM_CON_1, 112 PWM_PERIOD_MASK | PWM_HIGH_WIDTH_MASK, value); 113 mtk_disp_pwm_update_bits(mdp, DISP_PWM_COMMIT, PWM_COMMIT_MASK, 1); 114 mtk_disp_pwm_update_bits(mdp, DISP_PWM_COMMIT, PWM_COMMIT_MASK, 0); 115 116 clk_disable(mdp->clk_mm); 117 clk_disable(mdp->clk_main); 118 119 return 0; 120 } 121 122 static int mtk_disp_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 123 { 124 struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); 125 int err; 126 127 err = clk_enable(mdp->clk_main); 128 if (err < 0) 129 return err; 130 131 err = clk_enable(mdp->clk_mm); 132 if (err < 0) { 133 clk_disable(mdp->clk_main); 134 return err; 135 } 136 137 mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, PWM_ENABLE_MASK, 1); 138 139 return 0; 140 } 141 142 static void mtk_disp_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 143 { 144 struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); 145 146 mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, PWM_ENABLE_MASK, 0); 147 148 clk_disable(mdp->clk_mm); 149 clk_disable(mdp->clk_main); 150 } 151 152 static const struct pwm_ops mtk_disp_pwm_ops = { 153 .config = mtk_disp_pwm_config, 154 .enable = mtk_disp_pwm_enable, 155 .disable = mtk_disp_pwm_disable, 156 .owner = THIS_MODULE, 157 }; 158 159 static int mtk_disp_pwm_probe(struct platform_device *pdev) 160 { 161 struct mtk_disp_pwm *mdp; 162 struct resource *r; 163 int ret; 164 165 mdp = devm_kzalloc(&pdev->dev, sizeof(*mdp), GFP_KERNEL); 166 if (!mdp) 167 return -ENOMEM; 168 169 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 170 mdp->base = devm_ioremap_resource(&pdev->dev, r); 171 if (IS_ERR(mdp->base)) 172 return PTR_ERR(mdp->base); 173 174 mdp->clk_main = devm_clk_get(&pdev->dev, "main"); 175 if (IS_ERR(mdp->clk_main)) 176 return PTR_ERR(mdp->clk_main); 177 178 mdp->clk_mm = devm_clk_get(&pdev->dev, "mm"); 179 if (IS_ERR(mdp->clk_mm)) 180 return PTR_ERR(mdp->clk_mm); 181 182 ret = clk_prepare(mdp->clk_main); 183 if (ret < 0) 184 return ret; 185 186 ret = clk_prepare(mdp->clk_mm); 187 if (ret < 0) 188 goto disable_clk_main; 189 190 mdp->chip.dev = &pdev->dev; 191 mdp->chip.ops = &mtk_disp_pwm_ops; 192 mdp->chip.base = -1; 193 mdp->chip.npwm = 1; 194 195 ret = pwmchip_add(&mdp->chip); 196 if (ret < 0) { 197 dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); 198 goto disable_clk_mm; 199 } 200 201 platform_set_drvdata(pdev, mdp); 202 203 return 0; 204 205 disable_clk_mm: 206 clk_unprepare(mdp->clk_mm); 207 disable_clk_main: 208 clk_unprepare(mdp->clk_main); 209 return ret; 210 } 211 212 static int mtk_disp_pwm_remove(struct platform_device *pdev) 213 { 214 struct mtk_disp_pwm *mdp = platform_get_drvdata(pdev); 215 int ret; 216 217 ret = pwmchip_remove(&mdp->chip); 218 clk_unprepare(mdp->clk_mm); 219 clk_unprepare(mdp->clk_main); 220 221 return ret; 222 } 223 224 static const struct of_device_id mtk_disp_pwm_of_match[] = { 225 { .compatible = "mediatek,mt8173-disp-pwm" }, 226 { .compatible = "mediatek,mt6595-disp-pwm" }, 227 { } 228 }; 229 MODULE_DEVICE_TABLE(of, mtk_disp_pwm_of_match); 230 231 static struct platform_driver mtk_disp_pwm_driver = { 232 .driver = { 233 .name = "mediatek-disp-pwm", 234 .of_match_table = mtk_disp_pwm_of_match, 235 }, 236 .probe = mtk_disp_pwm_probe, 237 .remove = mtk_disp_pwm_remove, 238 }; 239 module_platform_driver(mtk_disp_pwm_driver); 240 241 MODULE_AUTHOR("YH Huang <yh.huang@mediatek.com>"); 242 MODULE_DESCRIPTION("MediaTek SoC display PWM driver"); 243 MODULE_LICENSE("GPL v2"); 244