linux/drivers/pwm/pwm-mtk-disp.c
<<
>>
Prefs
   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        /*
  78         * Find period, high_width and clk_div to suit duty_ns and period_ns.
  79         * Calculate proper div value to keep period value in the bound.
  80         *
  81         * period_ns = 10^9 * (clk_div + 1) * (period + 1) / PWM_CLK_RATE
  82         * duty_ns = 10^9 * (clk_div + 1) * high_width / PWM_CLK_RATE
  83         *
  84         * period = (PWM_CLK_RATE * period_ns) / (10^9 * (clk_div + 1)) - 1
  85         * high_width = (PWM_CLK_RATE * duty_ns) / (10^9 * (clk_div + 1))
  86         */
  87        rate = clk_get_rate(mdp->clk_main);
  88        clk_div = div_u64(rate * period_ns, NSEC_PER_SEC) >>
  89                          PWM_PERIOD_BIT_WIDTH;
  90        if (clk_div > PWM_CLKDIV_MAX)
  91                return -EINVAL;
  92
  93        div = NSEC_PER_SEC * (clk_div + 1);
  94        period = div64_u64(rate * period_ns, div);
  95        if (period > 0)
  96                period--;
  97
  98        high_width = div64_u64(rate * duty_ns, div);
  99        value = period | (high_width << PWM_HIGH_WIDTH_SHIFT);
 100
 101        err = clk_enable(mdp->clk_main);
 102        if (err < 0)
 103                return err;
 104
 105        err = clk_enable(mdp->clk_mm);
 106        if (err < 0) {
 107                clk_disable(mdp->clk_main);
 108                return err;
 109        }
 110
 111        mtk_disp_pwm_update_bits(mdp, mdp->data->con0,
 112                                 PWM_CLKDIV_MASK,
 113                                 clk_div << PWM_CLKDIV_SHIFT);
 114        mtk_disp_pwm_update_bits(mdp, mdp->data->con1,
 115                                 PWM_PERIOD_MASK | PWM_HIGH_WIDTH_MASK,
 116                                 value);
 117
 118        if (mdp->data->has_commit) {
 119                mtk_disp_pwm_update_bits(mdp, mdp->data->commit,
 120                                         mdp->data->commit_mask,
 121                                         mdp->data->commit_mask);
 122                mtk_disp_pwm_update_bits(mdp, mdp->data->commit,
 123                                         mdp->data->commit_mask,
 124                                         0x0);
 125        }
 126
 127        clk_disable(mdp->clk_mm);
 128        clk_disable(mdp->clk_main);
 129
 130        return 0;
 131}
 132
 133static int mtk_disp_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 134{
 135        struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip);
 136        int err;
 137
 138        err = clk_enable(mdp->clk_main);
 139        if (err < 0)
 140                return err;
 141
 142        err = clk_enable(mdp->clk_mm);
 143        if (err < 0) {
 144                clk_disable(mdp->clk_main);
 145                return err;
 146        }
 147
 148        mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask,
 149                                 mdp->data->enable_mask);
 150
 151        return 0;
 152}
 153
 154static void mtk_disp_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 155{
 156        struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip);
 157
 158        mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask,
 159                                 0x0);
 160
 161        clk_disable(mdp->clk_mm);
 162        clk_disable(mdp->clk_main);
 163}
 164
 165static const struct pwm_ops mtk_disp_pwm_ops = {
 166        .config = mtk_disp_pwm_config,
 167        .enable = mtk_disp_pwm_enable,
 168        .disable = mtk_disp_pwm_disable,
 169        .owner = THIS_MODULE,
 170};
 171
 172static int mtk_disp_pwm_probe(struct platform_device *pdev)
 173{
 174        struct mtk_disp_pwm *mdp;
 175        struct resource *r;
 176        int ret;
 177
 178        mdp = devm_kzalloc(&pdev->dev, sizeof(*mdp), GFP_KERNEL);
 179        if (!mdp)
 180                return -ENOMEM;
 181
 182        mdp->data = of_device_get_match_data(&pdev->dev);
 183
 184        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 185        mdp->base = devm_ioremap_resource(&pdev->dev, r);
 186        if (IS_ERR(mdp->base))
 187                return PTR_ERR(mdp->base);
 188
 189        mdp->clk_main = devm_clk_get(&pdev->dev, "main");
 190        if (IS_ERR(mdp->clk_main))
 191                return PTR_ERR(mdp->clk_main);
 192
 193        mdp->clk_mm = devm_clk_get(&pdev->dev, "mm");
 194        if (IS_ERR(mdp->clk_mm))
 195                return PTR_ERR(mdp->clk_mm);
 196
 197        ret = clk_prepare(mdp->clk_main);
 198        if (ret < 0)
 199                return ret;
 200
 201        ret = clk_prepare(mdp->clk_mm);
 202        if (ret < 0)
 203                goto disable_clk_main;
 204
 205        mdp->chip.dev = &pdev->dev;
 206        mdp->chip.ops = &mtk_disp_pwm_ops;
 207        mdp->chip.base = -1;
 208        mdp->chip.npwm = 1;
 209
 210        ret = pwmchip_add(&mdp->chip);
 211        if (ret < 0) {
 212                dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
 213                goto disable_clk_mm;
 214        }
 215
 216        platform_set_drvdata(pdev, mdp);
 217
 218        /*
 219         * For MT2701, disable double buffer before writing register
 220         * and select manual mode and use PWM_PERIOD/PWM_HIGH_WIDTH.
 221         */
 222        if (!mdp->data->has_commit) {
 223                mtk_disp_pwm_update_bits(mdp, mdp->data->bls_debug,
 224                                         mdp->data->bls_debug_mask,
 225                                         mdp->data->bls_debug_mask);
 226                mtk_disp_pwm_update_bits(mdp, mdp->data->con0,
 227                                         mdp->data->con0_sel,
 228                                         mdp->data->con0_sel);
 229        }
 230
 231        return 0;
 232
 233disable_clk_mm:
 234        clk_unprepare(mdp->clk_mm);
 235disable_clk_main:
 236        clk_unprepare(mdp->clk_main);
 237        return ret;
 238}
 239
 240static int mtk_disp_pwm_remove(struct platform_device *pdev)
 241{
 242        struct mtk_disp_pwm *mdp = platform_get_drvdata(pdev);
 243        int ret;
 244
 245        ret = pwmchip_remove(&mdp->chip);
 246        clk_unprepare(mdp->clk_mm);
 247        clk_unprepare(mdp->clk_main);
 248
 249        return ret;
 250}
 251
 252static const struct mtk_pwm_data mt2701_pwm_data = {
 253        .enable_mask = BIT(16),
 254        .con0 = 0xa8,
 255        .con0_sel = 0x2,
 256        .con1 = 0xac,
 257        .has_commit = false,
 258        .bls_debug = 0xb0,
 259        .bls_debug_mask = 0x3,
 260};
 261
 262static const struct mtk_pwm_data mt8173_pwm_data = {
 263        .enable_mask = BIT(0),
 264        .con0 = 0x10,
 265        .con0_sel = 0x0,
 266        .con1 = 0x14,
 267        .has_commit = true,
 268        .commit = 0x8,
 269        .commit_mask = 0x1,
 270};
 271
 272static const struct mtk_pwm_data mt8183_pwm_data = {
 273        .enable_mask = BIT(0),
 274        .con0 = 0x18,
 275        .con0_sel = 0x0,
 276        .con1 = 0x1c,
 277        .has_commit = false,
 278        .bls_debug = 0x80,
 279        .bls_debug_mask = 0x3,
 280};
 281
 282static const struct of_device_id mtk_disp_pwm_of_match[] = {
 283        { .compatible = "mediatek,mt2701-disp-pwm", .data = &mt2701_pwm_data},
 284        { .compatible = "mediatek,mt6595-disp-pwm", .data = &mt8173_pwm_data},
 285        { .compatible = "mediatek,mt8173-disp-pwm", .data = &mt8173_pwm_data},
 286        { .compatible = "mediatek,mt8183-disp-pwm", .data = &mt8183_pwm_data},
 287        { }
 288};
 289MODULE_DEVICE_TABLE(of, mtk_disp_pwm_of_match);
 290
 291static struct platform_driver mtk_disp_pwm_driver = {
 292        .driver = {
 293                .name = "mediatek-disp-pwm",
 294                .of_match_table = mtk_disp_pwm_of_match,
 295        },
 296        .probe = mtk_disp_pwm_probe,
 297        .remove = mtk_disp_pwm_remove,
 298};
 299module_platform_driver(mtk_disp_pwm_driver);
 300
 301MODULE_AUTHOR("YH Huang <yh.huang@mediatek.com>");
 302MODULE_DESCRIPTION("MediaTek SoC display PWM driver");
 303MODULE_LICENSE("GPL v2");
 304