200 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * simple driver for PWM (Pulse Width Modulator) controller4 *5 * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>6 */7 8#include <linux/bitfield.h>9#include <linux/bitops.h>10#include <linux/clk.h>11#include <linux/delay.h>12#include <linux/err.h>13#include <linux/io.h>14#include <linux/kernel.h>15#include <linux/module.h>16#include <linux/of.h>17#include <linux/platform_device.h>18#include <linux/pwm.h>19#include <linux/slab.h>20 21#define MX1_PWMC 0x00 /* PWM Control Register */22#define MX1_PWMS 0x04 /* PWM Sample Register */23#define MX1_PWMP 0x08 /* PWM Period Register */24 25#define MX1_PWMC_EN BIT(4)26 27struct pwm_imx1_chip {28 struct clk *clk_ipg;29 struct clk *clk_per;30 void __iomem *mmio_base;31};32 33static inline struct pwm_imx1_chip *to_pwm_imx1_chip(struct pwm_chip *chip)34{35 return pwmchip_get_drvdata(chip);36}37 38static int pwm_imx1_clk_prepare_enable(struct pwm_chip *chip)39{40 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);41 int ret;42 43 ret = clk_prepare_enable(imx->clk_ipg);44 if (ret)45 return ret;46 47 ret = clk_prepare_enable(imx->clk_per);48 if (ret) {49 clk_disable_unprepare(imx->clk_ipg);50 return ret;51 }52 53 return 0;54}55 56static void pwm_imx1_clk_disable_unprepare(struct pwm_chip *chip)57{58 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);59 60 clk_disable_unprepare(imx->clk_per);61 clk_disable_unprepare(imx->clk_ipg);62}63 64static int pwm_imx1_config(struct pwm_chip *chip,65 struct pwm_device *pwm, u64 duty_ns, u64 period_ns)66{67 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);68 u32 max, p;69 70 /*71 * The PWM subsystem allows for exact frequencies. However,72 * I cannot connect a scope on my device to the PWM line and73 * thus cannot provide the program the PWM controller74 * exactly. Instead, I'm relying on the fact that the75 * Bootloader (u-boot or WinCE+haret) has programmed the PWM76 * function group already. So I'll just modify the PWM sample77 * register to follow the ratio of duty_ns vs. period_ns78 * accordingly.79 *80 * This is good enough for programming the brightness of81 * the LCD backlight.82 *83 * The real implementation would divide PERCLK[0] first by84 * both the prescaler (/1 .. /128) and then by CLKSEL85 * (/2 .. /16).86 */87 max = readl(imx->mmio_base + MX1_PWMP);88 p = mul_u64_u64_div_u64(max, duty_ns, period_ns);89 90 writel(max - p, imx->mmio_base + MX1_PWMS);91 92 return 0;93}94 95static int pwm_imx1_enable(struct pwm_chip *chip, struct pwm_device *pwm)96{97 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);98 u32 value;99 int ret;100 101 ret = pwm_imx1_clk_prepare_enable(chip);102 if (ret < 0)103 return ret;104 105 value = readl(imx->mmio_base + MX1_PWMC);106 value |= MX1_PWMC_EN;107 writel(value, imx->mmio_base + MX1_PWMC);108 109 return 0;110}111 112static void pwm_imx1_disable(struct pwm_chip *chip, struct pwm_device *pwm)113{114 struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);115 u32 value;116 117 value = readl(imx->mmio_base + MX1_PWMC);118 value &= ~MX1_PWMC_EN;119 writel(value, imx->mmio_base + MX1_PWMC);120 121 pwm_imx1_clk_disable_unprepare(chip);122}123 124static int pwm_imx1_apply(struct pwm_chip *chip, struct pwm_device *pwm,125 const struct pwm_state *state)126{127 int err;128 129 if (state->polarity != PWM_POLARITY_NORMAL)130 return -EINVAL;131 132 if (!state->enabled) {133 if (pwm->state.enabled)134 pwm_imx1_disable(chip, pwm);135 136 return 0;137 }138 139 err = pwm_imx1_config(chip, pwm, state->duty_cycle, state->period);140 if (err)141 return err;142 143 if (!pwm->state.enabled)144 return pwm_imx1_enable(chip, pwm);145 146 return 0;147}148 149static const struct pwm_ops pwm_imx1_ops = {150 .apply = pwm_imx1_apply,151};152 153static const struct of_device_id pwm_imx1_dt_ids[] = {154 { .compatible = "fsl,imx1-pwm", },155 { /* sentinel */ }156};157MODULE_DEVICE_TABLE(of, pwm_imx1_dt_ids);158 159static int pwm_imx1_probe(struct platform_device *pdev)160{161 struct pwm_chip *chip;162 struct pwm_imx1_chip *imx;163 164 chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*imx));165 if (IS_ERR(chip))166 return PTR_ERR(chip);167 imx = to_pwm_imx1_chip(chip);168 169 imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");170 if (IS_ERR(imx->clk_ipg))171 return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_ipg),172 "getting ipg clock failed\n");173 174 imx->clk_per = devm_clk_get(&pdev->dev, "per");175 if (IS_ERR(imx->clk_per))176 return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_per),177 "failed to get peripheral clock\n");178 179 chip->ops = &pwm_imx1_ops;180 181 imx->mmio_base = devm_platform_ioremap_resource(pdev, 0);182 if (IS_ERR(imx->mmio_base))183 return PTR_ERR(imx->mmio_base);184 185 return devm_pwmchip_add(&pdev->dev, chip);186}187 188static struct platform_driver pwm_imx1_driver = {189 .driver = {190 .name = "pwm-imx1",191 .of_match_table = pwm_imx1_dt_ids,192 },193 .probe = pwm_imx1_probe,194};195module_platform_driver(pwm_imx1_driver);196 197MODULE_DESCRIPTION("i.MX1 and i.MX21 Pulse Width Modulator driver");198MODULE_LICENSE("GPL v2");199MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");200