18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * MediaTek display pulse-width-modulation controller driver. 48c2ecf20Sopenharmony_ci * Copyright (c) 2015 MediaTek Inc. 58c2ecf20Sopenharmony_ci * Author: YH Huang <yh.huang@mediatek.com> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/clk.h> 98c2ecf20Sopenharmony_ci#include <linux/err.h> 108c2ecf20Sopenharmony_ci#include <linux/io.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/of.h> 138c2ecf20Sopenharmony_ci#include <linux/of_device.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci#include <linux/pwm.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define DISP_PWM_EN 0x00 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define PWM_CLKDIV_SHIFT 16 218c2ecf20Sopenharmony_ci#define PWM_CLKDIV_MAX 0x3ff 228c2ecf20Sopenharmony_ci#define PWM_CLKDIV_MASK (PWM_CLKDIV_MAX << PWM_CLKDIV_SHIFT) 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define PWM_PERIOD_BIT_WIDTH 12 258c2ecf20Sopenharmony_ci#define PWM_PERIOD_MASK ((1 << PWM_PERIOD_BIT_WIDTH) - 1) 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define PWM_HIGH_WIDTH_SHIFT 16 288c2ecf20Sopenharmony_ci#define PWM_HIGH_WIDTH_MASK (0x1fff << PWM_HIGH_WIDTH_SHIFT) 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct mtk_pwm_data { 318c2ecf20Sopenharmony_ci u32 enable_mask; 328c2ecf20Sopenharmony_ci unsigned int con0; 338c2ecf20Sopenharmony_ci u32 con0_sel; 348c2ecf20Sopenharmony_ci unsigned int con1; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci bool has_commit; 378c2ecf20Sopenharmony_ci unsigned int commit; 388c2ecf20Sopenharmony_ci unsigned int commit_mask; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci unsigned int bls_debug; 418c2ecf20Sopenharmony_ci u32 bls_debug_mask; 428c2ecf20Sopenharmony_ci}; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistruct mtk_disp_pwm { 458c2ecf20Sopenharmony_ci struct pwm_chip chip; 468c2ecf20Sopenharmony_ci const struct mtk_pwm_data *data; 478c2ecf20Sopenharmony_ci struct clk *clk_main; 488c2ecf20Sopenharmony_ci struct clk *clk_mm; 498c2ecf20Sopenharmony_ci void __iomem *base; 508c2ecf20Sopenharmony_ci}; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic inline struct mtk_disp_pwm *to_mtk_disp_pwm(struct pwm_chip *chip) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci return container_of(chip, struct mtk_disp_pwm, chip); 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic void mtk_disp_pwm_update_bits(struct mtk_disp_pwm *mdp, u32 offset, 588c2ecf20Sopenharmony_ci u32 mask, u32 data) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci void __iomem *address = mdp->base + offset; 618c2ecf20Sopenharmony_ci u32 value; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci value = readl(address); 648c2ecf20Sopenharmony_ci value &= ~mask; 658c2ecf20Sopenharmony_ci value |= data; 668c2ecf20Sopenharmony_ci writel(value, address); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int mtk_disp_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 708c2ecf20Sopenharmony_ci int duty_ns, int period_ns) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); 738c2ecf20Sopenharmony_ci u32 clk_div, period, high_width, value; 748c2ecf20Sopenharmony_ci u64 div, rate; 758c2ecf20Sopenharmony_ci int err; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci err = clk_prepare_enable(mdp->clk_main); 788c2ecf20Sopenharmony_ci if (err < 0) { 798c2ecf20Sopenharmony_ci dev_err(chip->dev, "Can't enable mdp->clk_main: %pe\n", ERR_PTR(err)); 808c2ecf20Sopenharmony_ci return err; 818c2ecf20Sopenharmony_ci } 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci err = clk_prepare_enable(mdp->clk_mm); 848c2ecf20Sopenharmony_ci if (err < 0) { 858c2ecf20Sopenharmony_ci dev_err(chip->dev, "Can't enable mdp->clk_mm: %pe\n", ERR_PTR(err)); 868c2ecf20Sopenharmony_ci clk_disable_unprepare(mdp->clk_main); 878c2ecf20Sopenharmony_ci return err; 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci /* 918c2ecf20Sopenharmony_ci * Find period, high_width and clk_div to suit duty_ns and period_ns. 928c2ecf20Sopenharmony_ci * Calculate proper div value to keep period value in the bound. 938c2ecf20Sopenharmony_ci * 948c2ecf20Sopenharmony_ci * period_ns = 10^9 * (clk_div + 1) * (period + 1) / PWM_CLK_RATE 958c2ecf20Sopenharmony_ci * duty_ns = 10^9 * (clk_div + 1) * high_width / PWM_CLK_RATE 968c2ecf20Sopenharmony_ci * 978c2ecf20Sopenharmony_ci * period = (PWM_CLK_RATE * period_ns) / (10^9 * (clk_div + 1)) - 1 988c2ecf20Sopenharmony_ci * high_width = (PWM_CLK_RATE * duty_ns) / (10^9 * (clk_div + 1)) 998c2ecf20Sopenharmony_ci */ 1008c2ecf20Sopenharmony_ci rate = clk_get_rate(mdp->clk_main); 1018c2ecf20Sopenharmony_ci clk_div = div_u64(rate * period_ns, NSEC_PER_SEC) >> 1028c2ecf20Sopenharmony_ci PWM_PERIOD_BIT_WIDTH; 1038c2ecf20Sopenharmony_ci if (clk_div > PWM_CLKDIV_MAX) { 1048c2ecf20Sopenharmony_ci clk_disable_unprepare(mdp->clk_mm); 1058c2ecf20Sopenharmony_ci clk_disable_unprepare(mdp->clk_main); 1068c2ecf20Sopenharmony_ci return -EINVAL; 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci div = NSEC_PER_SEC * (clk_div + 1); 1108c2ecf20Sopenharmony_ci period = div64_u64(rate * period_ns, div); 1118c2ecf20Sopenharmony_ci if (period > 0) 1128c2ecf20Sopenharmony_ci period--; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci high_width = div64_u64(rate * duty_ns, div); 1158c2ecf20Sopenharmony_ci value = period | (high_width << PWM_HIGH_WIDTH_SHIFT); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (mdp->data->bls_debug && !mdp->data->has_commit) { 1188c2ecf20Sopenharmony_ci /* 1198c2ecf20Sopenharmony_ci * For MT2701, disable double buffer before writing register 1208c2ecf20Sopenharmony_ci * and select manual mode and use PWM_PERIOD/PWM_HIGH_WIDTH. 1218c2ecf20Sopenharmony_ci */ 1228c2ecf20Sopenharmony_ci mtk_disp_pwm_update_bits(mdp, mdp->data->bls_debug, 1238c2ecf20Sopenharmony_ci mdp->data->bls_debug_mask, 1248c2ecf20Sopenharmony_ci mdp->data->bls_debug_mask); 1258c2ecf20Sopenharmony_ci mtk_disp_pwm_update_bits(mdp, mdp->data->con0, 1268c2ecf20Sopenharmony_ci mdp->data->con0_sel, 1278c2ecf20Sopenharmony_ci mdp->data->con0_sel); 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci mtk_disp_pwm_update_bits(mdp, mdp->data->con0, 1318c2ecf20Sopenharmony_ci PWM_CLKDIV_MASK, 1328c2ecf20Sopenharmony_ci clk_div << PWM_CLKDIV_SHIFT); 1338c2ecf20Sopenharmony_ci mtk_disp_pwm_update_bits(mdp, mdp->data->con1, 1348c2ecf20Sopenharmony_ci PWM_PERIOD_MASK | PWM_HIGH_WIDTH_MASK, 1358c2ecf20Sopenharmony_ci value); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci if (mdp->data->has_commit) { 1388c2ecf20Sopenharmony_ci mtk_disp_pwm_update_bits(mdp, mdp->data->commit, 1398c2ecf20Sopenharmony_ci mdp->data->commit_mask, 1408c2ecf20Sopenharmony_ci mdp->data->commit_mask); 1418c2ecf20Sopenharmony_ci mtk_disp_pwm_update_bits(mdp, mdp->data->commit, 1428c2ecf20Sopenharmony_ci mdp->data->commit_mask, 1438c2ecf20Sopenharmony_ci 0x0); 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci clk_disable_unprepare(mdp->clk_mm); 1478c2ecf20Sopenharmony_ci clk_disable_unprepare(mdp->clk_main); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci return 0; 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic int mtk_disp_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); 1558c2ecf20Sopenharmony_ci int err; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci err = clk_prepare_enable(mdp->clk_main); 1588c2ecf20Sopenharmony_ci if (err < 0) { 1598c2ecf20Sopenharmony_ci dev_err(chip->dev, "Can't enable mdp->clk_main: %pe\n", ERR_PTR(err)); 1608c2ecf20Sopenharmony_ci return err; 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci err = clk_prepare_enable(mdp->clk_mm); 1648c2ecf20Sopenharmony_ci if (err < 0) { 1658c2ecf20Sopenharmony_ci dev_err(chip->dev, "Can't enable mdp->clk_mm: %pe\n", ERR_PTR(err)); 1668c2ecf20Sopenharmony_ci clk_disable_unprepare(mdp->clk_main); 1678c2ecf20Sopenharmony_ci return err; 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask, 1718c2ecf20Sopenharmony_ci mdp->data->enable_mask); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci return 0; 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic void mtk_disp_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 1778c2ecf20Sopenharmony_ci{ 1788c2ecf20Sopenharmony_ci struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, mdp->data->enable_mask, 1818c2ecf20Sopenharmony_ci 0x0); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci clk_disable_unprepare(mdp->clk_mm); 1848c2ecf20Sopenharmony_ci clk_disable_unprepare(mdp->clk_main); 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic const struct pwm_ops mtk_disp_pwm_ops = { 1888c2ecf20Sopenharmony_ci .config = mtk_disp_pwm_config, 1898c2ecf20Sopenharmony_ci .enable = mtk_disp_pwm_enable, 1908c2ecf20Sopenharmony_ci .disable = mtk_disp_pwm_disable, 1918c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1928c2ecf20Sopenharmony_ci}; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic int mtk_disp_pwm_probe(struct platform_device *pdev) 1958c2ecf20Sopenharmony_ci{ 1968c2ecf20Sopenharmony_ci struct mtk_disp_pwm *mdp; 1978c2ecf20Sopenharmony_ci struct resource *r; 1988c2ecf20Sopenharmony_ci int ret; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci mdp = devm_kzalloc(&pdev->dev, sizeof(*mdp), GFP_KERNEL); 2018c2ecf20Sopenharmony_ci if (!mdp) 2028c2ecf20Sopenharmony_ci return -ENOMEM; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci mdp->data = of_device_get_match_data(&pdev->dev); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2078c2ecf20Sopenharmony_ci mdp->base = devm_ioremap_resource(&pdev->dev, r); 2088c2ecf20Sopenharmony_ci if (IS_ERR(mdp->base)) 2098c2ecf20Sopenharmony_ci return PTR_ERR(mdp->base); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci mdp->clk_main = devm_clk_get(&pdev->dev, "main"); 2128c2ecf20Sopenharmony_ci if (IS_ERR(mdp->clk_main)) 2138c2ecf20Sopenharmony_ci return PTR_ERR(mdp->clk_main); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci mdp->clk_mm = devm_clk_get(&pdev->dev, "mm"); 2168c2ecf20Sopenharmony_ci if (IS_ERR(mdp->clk_mm)) 2178c2ecf20Sopenharmony_ci return PTR_ERR(mdp->clk_mm); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci mdp->chip.dev = &pdev->dev; 2208c2ecf20Sopenharmony_ci mdp->chip.ops = &mtk_disp_pwm_ops; 2218c2ecf20Sopenharmony_ci mdp->chip.base = -1; 2228c2ecf20Sopenharmony_ci mdp->chip.npwm = 1; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci ret = pwmchip_add(&mdp->chip); 2258c2ecf20Sopenharmony_ci if (ret < 0) { 2268c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "pwmchip_add() failed: %pe\n", ERR_PTR(ret)); 2278c2ecf20Sopenharmony_ci return ret; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, mdp); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci return 0; 2338c2ecf20Sopenharmony_ci} 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_cistatic int mtk_disp_pwm_remove(struct platform_device *pdev) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci struct mtk_disp_pwm *mdp = platform_get_drvdata(pdev); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci pwmchip_remove(&mdp->chip); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci return 0; 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cistatic const struct mtk_pwm_data mt2701_pwm_data = { 2458c2ecf20Sopenharmony_ci .enable_mask = BIT(16), 2468c2ecf20Sopenharmony_ci .con0 = 0xa8, 2478c2ecf20Sopenharmony_ci .con0_sel = 0x2, 2488c2ecf20Sopenharmony_ci .con1 = 0xac, 2498c2ecf20Sopenharmony_ci .has_commit = false, 2508c2ecf20Sopenharmony_ci .bls_debug = 0xb0, 2518c2ecf20Sopenharmony_ci .bls_debug_mask = 0x3, 2528c2ecf20Sopenharmony_ci}; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic const struct mtk_pwm_data mt8173_pwm_data = { 2558c2ecf20Sopenharmony_ci .enable_mask = BIT(0), 2568c2ecf20Sopenharmony_ci .con0 = 0x10, 2578c2ecf20Sopenharmony_ci .con0_sel = 0x0, 2588c2ecf20Sopenharmony_ci .con1 = 0x14, 2598c2ecf20Sopenharmony_ci .has_commit = true, 2608c2ecf20Sopenharmony_ci .commit = 0x8, 2618c2ecf20Sopenharmony_ci .commit_mask = 0x1, 2628c2ecf20Sopenharmony_ci}; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_cistatic const struct mtk_pwm_data mt8183_pwm_data = { 2658c2ecf20Sopenharmony_ci .enable_mask = BIT(0), 2668c2ecf20Sopenharmony_ci .con0 = 0x18, 2678c2ecf20Sopenharmony_ci .con0_sel = 0x0, 2688c2ecf20Sopenharmony_ci .con1 = 0x1c, 2698c2ecf20Sopenharmony_ci .has_commit = false, 2708c2ecf20Sopenharmony_ci .bls_debug = 0x80, 2718c2ecf20Sopenharmony_ci .bls_debug_mask = 0x3, 2728c2ecf20Sopenharmony_ci}; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic const struct of_device_id mtk_disp_pwm_of_match[] = { 2758c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt2701-disp-pwm", .data = &mt2701_pwm_data}, 2768c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt6595-disp-pwm", .data = &mt8173_pwm_data}, 2778c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt8173-disp-pwm", .data = &mt8173_pwm_data}, 2788c2ecf20Sopenharmony_ci { .compatible = "mediatek,mt8183-disp-pwm", .data = &mt8183_pwm_data}, 2798c2ecf20Sopenharmony_ci { } 2808c2ecf20Sopenharmony_ci}; 2818c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtk_disp_pwm_of_match); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic struct platform_driver mtk_disp_pwm_driver = { 2848c2ecf20Sopenharmony_ci .driver = { 2858c2ecf20Sopenharmony_ci .name = "mediatek-disp-pwm", 2868c2ecf20Sopenharmony_ci .of_match_table = mtk_disp_pwm_of_match, 2878c2ecf20Sopenharmony_ci }, 2888c2ecf20Sopenharmony_ci .probe = mtk_disp_pwm_probe, 2898c2ecf20Sopenharmony_ci .remove = mtk_disp_pwm_remove, 2908c2ecf20Sopenharmony_ci}; 2918c2ecf20Sopenharmony_cimodule_platform_driver(mtk_disp_pwm_driver); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ciMODULE_AUTHOR("YH Huang <yh.huang@mediatek.com>"); 2948c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MediaTek SoC display PWM driver"); 2958c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 296