1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * MediaTek display pulse-width-modulation controller driver. 4 * Copyright (c) 2015 MediaTek Inc. 5 * Author: YH Huang <yh.huang@mediatek.com> 6 */ 7 8#include <linux/clk.h> 9#include <linux/err.h> 10#include <linux/io.h> 11#include <linux/module.h> 12#include <linux/of.h> 13#include <linux/of_device.h> 14#include <linux/platform_device.h> 15#include <linux/pwm.h> 16#include <linux/slab.h> 17 18#define DISP_PWM_EN 0x00 19 20#define PWM_CLKDIV_SHIFT 16 21#define PWM_CLKDIV_MAX 0x3ff 22#define PWM_CLKDIV_MASK (PWM_CLKDIV_MAX << PWM_CLKDIV_SHIFT) 23 24#define PWM_PERIOD_BIT_WIDTH 12 25#define PWM_PERIOD_MASK ((1 << PWM_PERIOD_BIT_WIDTH) - 1) 26 27#define PWM_HIGH_WIDTH_SHIFT 16 28#define PWM_HIGH_WIDTH_MASK (0x1fff << PWM_HIGH_WIDTH_SHIFT) 29 30struct mtk_pwm_data { 31 u32 enable_mask; 32 unsigned int con0; 33 u32 con0_sel; 34 unsigned int con1; 35 36 bool has_commit; 37 unsigned int commit; 38 unsigned int commit_mask; 39 40 unsigned int bls_debug; 41 u32 bls_debug_mask; 42}; 43 44struct mtk_disp_pwm { 45 struct pwm_chip chip; 46 const struct mtk_pwm_data *data; 47 struct clk *clk_main; 48 struct clk *clk_mm; 49 void __iomem *base; 50}; 51 52static inline struct mtk_disp_pwm *to_mtk_disp_pwm(struct pwm_chip *chip) 53{ 54 return container_of(chip, struct mtk_disp_pwm, chip); 55} 56 57static void mtk_disp_pwm_update_bits(struct mtk_disp_pwm *mdp, u32 offset, 58 u32 mask, u32 data) 59{ 60 void __iomem *address = mdp->base + offset; 61 u32 value; 62 63 value = readl(address); 64 value &= ~mask; 65 value |= data; 66 writel(value, address); 67} 68 69static int mtk_disp_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 70 int duty_ns, int period_ns) 71{ 72 struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); 73 u32 clk_div, period, high_width, value; 74 u64 div, rate; 75 int err; 76 77 err = clk_prepare_enable(mdp->clk_main); 78 if (err < 0) { 79 dev_err(chip->dev, "Can't enable mdp->clk_main: %pe\n", ERR_PTR(err)); 80 return err; 81 } 82 83 err = clk_prepare_enable(mdp->clk_mm); 84 if (err < 0) { 85 dev_err(chip->dev, "Can't enable mdp->clk_mm: %pe\n", ERR_PTR(err)); 86 clk_disable_unprepare(mdp->clk_main); 87 return err; 88 } 89 90 /* 91 * Find period, high_width and clk_div to suit duty_ns and period_ns. 92 * Calculate proper div value to keep period value in the bound. 93 * 94 * period_ns = 10^9 * (clk_div + 1) * (period + 1) / PWM_CLK_RATE 95 * duty_ns = 10^9 * (clk_div + 1) * high_width / PWM_CLK_RATE 96 * 97 * period = (PWM_CLK_RATE * period_ns) / (10^9 * (clk_div + 1)) - 1 98 * high_width = (PWM_CLK_RATE * duty_ns) / (10^9 * (clk_div + 1)) 99 */ 100 rate = clk_get_rate(mdp->clk_main); 101 clk_div = div_u64(rate * period_ns, NSEC_PER_SEC) >> 102 PWM_PERIOD_BIT_WIDTH; 103 if (clk_div > PWM_CLKDIV_MAX) { 104 clk_disable_unprepare(mdp->clk_mm); 105 clk_disable_unprepare(mdp->clk_main); 106 return -EINVAL; 107 } 108 109 div = NSEC_PER_SEC * (clk_div + 1); 110 period = div64_u64(rate * period_ns, div); 111 if (period > 0) 112 period--; 113 114 high_width = div64_u64(rate * duty_ns, div); 115 value = period | (high_width << PWM_HIGH_WIDTH_SHIFT); 116 117 if (mdp->data->bls_debug && !mdp->data->has_commit) { 118 /* 119 * For MT2701, disable double buffer before writing register 120 * and select manual mode and use PWM_PERIOD/PWM_HIGH_WIDTH. 121 */ 122 mtk_disp_pwm_update_bits(mdp, mdp->data->bls_debug, 123 mdp->data->bls_debug_mask, 124 mdp->data->bls_debug_mask); 125 mtk_disp_pwm_update_bits(mdp, mdp->data->con0, 126 mdp->data->con0_sel, 127 mdp->data->con0_sel); 128 } 129 130 mtk_disp_pwm_update_bits(mdp, mdp->data->con0, 131 PWM_CLKDIV_MASK, 132 clk_div << PWM_CLKDIV_SHIFT); 133 mtk_disp_pwm_update_bits(mdp, mdp->data->con1, 134 PWM_PERIOD_MASK | PWM_HIGH_WIDTH_MASK, 135 value); 136 137 if (mdp->data->has_commit) { 138 mtk_disp_pwm_update_bits(mdp, mdp->data->commit, 139 mdp->data->commit_mask, 140 mdp->data->commit_mask); 141 mtk_disp_pwm_update_bits(mdp, mdp->data->commit, 142 mdp->data->commit_mask, 143 0x0); 144 } 145 146 clk_disable_unprepare(mdp->clk_mm); 147 clk_disable_unprepare(mdp->clk_main); 148 149 return 0; 150} 151 152static int mtk_disp_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 153{ 154 struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); 155 int err; 156 157 err = clk_prepare_enable(mdp->clk_main); 158 if (err < 0) { 159 dev_err(chip->dev, "Can't enable mdp->clk_main: %pe\n", ERR_PTR(err)); 160 return err; 161 } 162 163 err = clk_prepare_enable(mdp->clk_mm); 164 if (err < 0) { 165 dev_err(chip->dev, "Can't enable mdp->clk_mm: %pe\n", ERR_PTR(err)); 166 clk_disable_unprepare(mdp->clk_main); 167 return err; 168 } 169 170 mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask, 171 mdp->data->enable_mask); 172 173 return 0; 174} 175 176static void mtk_disp_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 177{ 178 struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); 179 180 mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask, 181 0x0); 182 183 clk_disable_unprepare(mdp->clk_mm); 184 clk_disable_unprepare(mdp->clk_main); 185} 186 187static const struct pwm_ops mtk_disp_pwm_ops = { 188 .config = mtk_disp_pwm_config, 189 .enable = mtk_disp_pwm_enable, 190 .disable = mtk_disp_pwm_disable, 191 .owner = THIS_MODULE, 192}; 193 194static int mtk_disp_pwm_probe(struct platform_device *pdev) 195{ 196 struct mtk_disp_pwm *mdp; 197 struct resource *r; 198 int ret; 199 200 mdp = devm_kzalloc(&pdev->dev, sizeof(*mdp), GFP_KERNEL); 201 if (!mdp) 202 return -ENOMEM; 203 204 mdp->data = of_device_get_match_data(&pdev->dev); 205 206 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 207 mdp->base = devm_ioremap_resource(&pdev->dev, r); 208 if (IS_ERR(mdp->base)) 209 return PTR_ERR(mdp->base); 210 211 mdp->clk_main = devm_clk_get(&pdev->dev, "main"); 212 if (IS_ERR(mdp->clk_main)) 213 return PTR_ERR(mdp->clk_main); 214 215 mdp->clk_mm = devm_clk_get(&pdev->dev, "mm"); 216 if (IS_ERR(mdp->clk_mm)) 217 return PTR_ERR(mdp->clk_mm); 218 219 mdp->chip.dev = &pdev->dev; 220 mdp->chip.ops = &mtk_disp_pwm_ops; 221 mdp->chip.base = -1; 222 mdp->chip.npwm = 1; 223 224 ret = pwmchip_add(&mdp->chip); 225 if (ret < 0) { 226 dev_err(&pdev->dev, "pwmchip_add() failed: %pe\n", ERR_PTR(ret)); 227 return ret; 228 } 229 230 platform_set_drvdata(pdev, mdp); 231 232 return 0; 233} 234 235static int mtk_disp_pwm_remove(struct platform_device *pdev) 236{ 237 struct mtk_disp_pwm *mdp = platform_get_drvdata(pdev); 238 239 pwmchip_remove(&mdp->chip); 240 241 return 0; 242} 243 244static const struct mtk_pwm_data mt2701_pwm_data = { 245 .enable_mask = BIT(16), 246 .con0 = 0xa8, 247 .con0_sel = 0x2, 248 .con1 = 0xac, 249 .has_commit = false, 250 .bls_debug = 0xb0, 251 .bls_debug_mask = 0x3, 252}; 253 254static const struct mtk_pwm_data mt8173_pwm_data = { 255 .enable_mask = BIT(0), 256 .con0 = 0x10, 257 .con0_sel = 0x0, 258 .con1 = 0x14, 259 .has_commit = true, 260 .commit = 0x8, 261 .commit_mask = 0x1, 262}; 263 264static const struct mtk_pwm_data mt8183_pwm_data = { 265 .enable_mask = BIT(0), 266 .con0 = 0x18, 267 .con0_sel = 0x0, 268 .con1 = 0x1c, 269 .has_commit = false, 270 .bls_debug = 0x80, 271 .bls_debug_mask = 0x3, 272}; 273 274static const struct of_device_id mtk_disp_pwm_of_match[] = { 275 { .compatible = "mediatek,mt2701-disp-pwm", .data = &mt2701_pwm_data}, 276 { .compatible = "mediatek,mt6595-disp-pwm", .data = &mt8173_pwm_data}, 277 { .compatible = "mediatek,mt8173-disp-pwm", .data = &mt8173_pwm_data}, 278 { .compatible = "mediatek,mt8183-disp-pwm", .data = &mt8183_pwm_data}, 279 { } 280}; 281MODULE_DEVICE_TABLE(of, mtk_disp_pwm_of_match); 282 283static struct platform_driver mtk_disp_pwm_driver = { 284 .driver = { 285 .name = "mediatek-disp-pwm", 286 .of_match_table = mtk_disp_pwm_of_match, 287 }, 288 .probe = mtk_disp_pwm_probe, 289 .remove = mtk_disp_pwm_remove, 290}; 291module_platform_driver(mtk_disp_pwm_driver); 292 293MODULE_AUTHOR("YH Huang <yh.huang@mediatek.com>"); 294MODULE_DESCRIPTION("MediaTek SoC display PWM driver"); 295MODULE_LICENSE("GPL v2"); 296