162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * simple driver for PWM (Pulse Width Modulator) controller 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/bitfield.h> 962306a36Sopenharmony_ci#include <linux/bitops.h> 1062306a36Sopenharmony_ci#include <linux/clk.h> 1162306a36Sopenharmony_ci#include <linux/delay.h> 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/io.h> 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/of.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci#include <linux/pwm.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define MX1_PWMC 0x00 /* PWM Control Register */ 2262306a36Sopenharmony_ci#define MX1_PWMS 0x04 /* PWM Sample Register */ 2362306a36Sopenharmony_ci#define MX1_PWMP 0x08 /* PWM Period Register */ 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define MX1_PWMC_EN BIT(4) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistruct pwm_imx1_chip { 2862306a36Sopenharmony_ci struct clk *clk_ipg; 2962306a36Sopenharmony_ci struct clk *clk_per; 3062306a36Sopenharmony_ci void __iomem *mmio_base; 3162306a36Sopenharmony_ci struct pwm_chip chip; 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define to_pwm_imx1_chip(chip) container_of(chip, struct pwm_imx1_chip, chip) 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic int pwm_imx1_clk_prepare_enable(struct pwm_chip *chip) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); 3962306a36Sopenharmony_ci int ret; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci ret = clk_prepare_enable(imx->clk_ipg); 4262306a36Sopenharmony_ci if (ret) 4362306a36Sopenharmony_ci return ret; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci ret = clk_prepare_enable(imx->clk_per); 4662306a36Sopenharmony_ci if (ret) { 4762306a36Sopenharmony_ci clk_disable_unprepare(imx->clk_ipg); 4862306a36Sopenharmony_ci return ret; 4962306a36Sopenharmony_ci } 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci return 0; 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic void pwm_imx1_clk_disable_unprepare(struct pwm_chip *chip) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci clk_disable_unprepare(imx->clk_per); 5962306a36Sopenharmony_ci clk_disable_unprepare(imx->clk_ipg); 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic int pwm_imx1_config(struct pwm_chip *chip, 6362306a36Sopenharmony_ci struct pwm_device *pwm, u64 duty_ns, u64 period_ns) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); 6662306a36Sopenharmony_ci u32 max, p; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci /* 6962306a36Sopenharmony_ci * The PWM subsystem allows for exact frequencies. However, 7062306a36Sopenharmony_ci * I cannot connect a scope on my device to the PWM line and 7162306a36Sopenharmony_ci * thus cannot provide the program the PWM controller 7262306a36Sopenharmony_ci * exactly. Instead, I'm relying on the fact that the 7362306a36Sopenharmony_ci * Bootloader (u-boot or WinCE+haret) has programmed the PWM 7462306a36Sopenharmony_ci * function group already. So I'll just modify the PWM sample 7562306a36Sopenharmony_ci * register to follow the ratio of duty_ns vs. period_ns 7662306a36Sopenharmony_ci * accordingly. 7762306a36Sopenharmony_ci * 7862306a36Sopenharmony_ci * This is good enough for programming the brightness of 7962306a36Sopenharmony_ci * the LCD backlight. 8062306a36Sopenharmony_ci * 8162306a36Sopenharmony_ci * The real implementation would divide PERCLK[0] first by 8262306a36Sopenharmony_ci * both the prescaler (/1 .. /128) and then by CLKSEL 8362306a36Sopenharmony_ci * (/2 .. /16). 8462306a36Sopenharmony_ci */ 8562306a36Sopenharmony_ci max = readl(imx->mmio_base + MX1_PWMP); 8662306a36Sopenharmony_ci p = mul_u64_u64_div_u64(max, duty_ns, period_ns); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci writel(max - p, imx->mmio_base + MX1_PWMS); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci return 0; 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic int pwm_imx1_enable(struct pwm_chip *chip, struct pwm_device *pwm) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); 9662306a36Sopenharmony_ci u32 value; 9762306a36Sopenharmony_ci int ret; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci ret = pwm_imx1_clk_prepare_enable(chip); 10062306a36Sopenharmony_ci if (ret < 0) 10162306a36Sopenharmony_ci return ret; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci value = readl(imx->mmio_base + MX1_PWMC); 10462306a36Sopenharmony_ci value |= MX1_PWMC_EN; 10562306a36Sopenharmony_ci writel(value, imx->mmio_base + MX1_PWMC); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci return 0; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic void pwm_imx1_disable(struct pwm_chip *chip, struct pwm_device *pwm) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); 11362306a36Sopenharmony_ci u32 value; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci value = readl(imx->mmio_base + MX1_PWMC); 11662306a36Sopenharmony_ci value &= ~MX1_PWMC_EN; 11762306a36Sopenharmony_ci writel(value, imx->mmio_base + MX1_PWMC); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci pwm_imx1_clk_disable_unprepare(chip); 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic int pwm_imx1_apply(struct pwm_chip *chip, struct pwm_device *pwm, 12362306a36Sopenharmony_ci const struct pwm_state *state) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci int err; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci if (state->polarity != PWM_POLARITY_NORMAL) 12862306a36Sopenharmony_ci return -EINVAL; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (!state->enabled) { 13162306a36Sopenharmony_ci if (pwm->state.enabled) 13262306a36Sopenharmony_ci pwm_imx1_disable(chip, pwm); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci return 0; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci err = pwm_imx1_config(chip, pwm, state->duty_cycle, state->period); 13862306a36Sopenharmony_ci if (err) 13962306a36Sopenharmony_ci return err; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (!pwm->state.enabled) 14262306a36Sopenharmony_ci return pwm_imx1_enable(chip, pwm); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic const struct pwm_ops pwm_imx1_ops = { 14862306a36Sopenharmony_ci .apply = pwm_imx1_apply, 14962306a36Sopenharmony_ci .owner = THIS_MODULE, 15062306a36Sopenharmony_ci}; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic const struct of_device_id pwm_imx1_dt_ids[] = { 15362306a36Sopenharmony_ci { .compatible = "fsl,imx1-pwm", }, 15462306a36Sopenharmony_ci { /* sentinel */ } 15562306a36Sopenharmony_ci}; 15662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, pwm_imx1_dt_ids); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic int pwm_imx1_probe(struct platform_device *pdev) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct pwm_imx1_chip *imx; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); 16362306a36Sopenharmony_ci if (!imx) 16462306a36Sopenharmony_ci return -ENOMEM; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); 16762306a36Sopenharmony_ci if (IS_ERR(imx->clk_ipg)) 16862306a36Sopenharmony_ci return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_ipg), 16962306a36Sopenharmony_ci "getting ipg clock failed\n"); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci imx->clk_per = devm_clk_get(&pdev->dev, "per"); 17262306a36Sopenharmony_ci if (IS_ERR(imx->clk_per)) 17362306a36Sopenharmony_ci return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_per), 17462306a36Sopenharmony_ci "failed to get peripheral clock\n"); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci imx->chip.ops = &pwm_imx1_ops; 17762306a36Sopenharmony_ci imx->chip.dev = &pdev->dev; 17862306a36Sopenharmony_ci imx->chip.npwm = 1; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci imx->mmio_base = devm_platform_ioremap_resource(pdev, 0); 18162306a36Sopenharmony_ci if (IS_ERR(imx->mmio_base)) 18262306a36Sopenharmony_ci return PTR_ERR(imx->mmio_base); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci return devm_pwmchip_add(&pdev->dev, &imx->chip); 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic struct platform_driver pwm_imx1_driver = { 18862306a36Sopenharmony_ci .driver = { 18962306a36Sopenharmony_ci .name = "pwm-imx1", 19062306a36Sopenharmony_ci .of_match_table = pwm_imx1_dt_ids, 19162306a36Sopenharmony_ci }, 19262306a36Sopenharmony_ci .probe = pwm_imx1_probe, 19362306a36Sopenharmony_ci}; 19462306a36Sopenharmony_cimodule_platform_driver(pwm_imx1_driver); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 19762306a36Sopenharmony_ciMODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); 198