Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * simple driver for PWM (Pulse Width Modulator) controller
0004  *
0005  * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>
0006  */
0007 
0008 #include <linux/bitfield.h>
0009 #include <linux/bitops.h>
0010 #include <linux/clk.h>
0011 #include <linux/delay.h>
0012 #include <linux/err.h>
0013 #include <linux/io.h>
0014 #include <linux/kernel.h>
0015 #include <linux/module.h>
0016 #include <linux/of.h>
0017 #include <linux/of_device.h>
0018 #include <linux/platform_device.h>
0019 #include <linux/pwm.h>
0020 #include <linux/slab.h>
0021 
0022 #define MX1_PWMC            0x00   /* PWM Control Register */
0023 #define MX1_PWMS            0x04   /* PWM Sample Register */
0024 #define MX1_PWMP            0x08   /* PWM Period Register */
0025 
0026 #define MX1_PWMC_EN         BIT(4)
0027 
0028 struct pwm_imx1_chip {
0029     struct clk *clk_ipg;
0030     struct clk *clk_per;
0031     void __iomem *mmio_base;
0032     struct pwm_chip chip;
0033 };
0034 
0035 #define to_pwm_imx1_chip(chip)  container_of(chip, struct pwm_imx1_chip, chip)
0036 
0037 static int pwm_imx1_clk_prepare_enable(struct pwm_chip *chip)
0038 {
0039     struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
0040     int ret;
0041 
0042     ret = clk_prepare_enable(imx->clk_ipg);
0043     if (ret)
0044         return ret;
0045 
0046     ret = clk_prepare_enable(imx->clk_per);
0047     if (ret) {
0048         clk_disable_unprepare(imx->clk_ipg);
0049         return ret;
0050     }
0051 
0052     return 0;
0053 }
0054 
0055 static void pwm_imx1_clk_disable_unprepare(struct pwm_chip *chip)
0056 {
0057     struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
0058 
0059     clk_disable_unprepare(imx->clk_per);
0060     clk_disable_unprepare(imx->clk_ipg);
0061 }
0062 
0063 static int pwm_imx1_config(struct pwm_chip *chip,
0064                struct pwm_device *pwm, u64 duty_ns, u64 period_ns)
0065 {
0066     struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
0067     u32 max, p;
0068 
0069     /*
0070      * The PWM subsystem allows for exact frequencies. However,
0071      * I cannot connect a scope on my device to the PWM line and
0072      * thus cannot provide the program the PWM controller
0073      * exactly. Instead, I'm relying on the fact that the
0074      * Bootloader (u-boot or WinCE+haret) has programmed the PWM
0075      * function group already. So I'll just modify the PWM sample
0076      * register to follow the ratio of duty_ns vs. period_ns
0077      * accordingly.
0078      *
0079      * This is good enough for programming the brightness of
0080      * the LCD backlight.
0081      *
0082      * The real implementation would divide PERCLK[0] first by
0083      * both the prescaler (/1 .. /128) and then by CLKSEL
0084      * (/2 .. /16).
0085      */
0086     max = readl(imx->mmio_base + MX1_PWMP);
0087     p = mul_u64_u64_div_u64(max, duty_ns, period_ns);
0088 
0089     writel(max - p, imx->mmio_base + MX1_PWMS);
0090 
0091     return 0;
0092 }
0093 
0094 static int pwm_imx1_enable(struct pwm_chip *chip, struct pwm_device *pwm)
0095 {
0096     struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
0097     u32 value;
0098     int ret;
0099 
0100     ret = pwm_imx1_clk_prepare_enable(chip);
0101     if (ret < 0)
0102         return ret;
0103 
0104     value = readl(imx->mmio_base + MX1_PWMC);
0105     value |= MX1_PWMC_EN;
0106     writel(value, imx->mmio_base + MX1_PWMC);
0107 
0108     return 0;
0109 }
0110 
0111 static void pwm_imx1_disable(struct pwm_chip *chip, struct pwm_device *pwm)
0112 {
0113     struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);
0114     u32 value;
0115 
0116     value = readl(imx->mmio_base + MX1_PWMC);
0117     value &= ~MX1_PWMC_EN;
0118     writel(value, imx->mmio_base + MX1_PWMC);
0119 
0120     pwm_imx1_clk_disable_unprepare(chip);
0121 }
0122 
0123 static int pwm_imx1_apply(struct pwm_chip *chip, struct pwm_device *pwm,
0124               const struct pwm_state *state)
0125 {
0126     int err;
0127 
0128     if (state->polarity != PWM_POLARITY_NORMAL)
0129         return -EINVAL;
0130 
0131     if (!state->enabled) {
0132         if (pwm->state.enabled)
0133             pwm_imx1_disable(chip, pwm);
0134 
0135         return 0;
0136     }
0137 
0138     err = pwm_imx1_config(chip, pwm, state->duty_cycle, state->period);
0139     if (err)
0140         return err;
0141 
0142     if (!pwm->state.enabled)
0143         return pwm_imx1_enable(chip, pwm);
0144 
0145     return 0;
0146 }
0147 
0148 static const struct pwm_ops pwm_imx1_ops = {
0149     .apply = pwm_imx1_apply,
0150     .owner = THIS_MODULE,
0151 };
0152 
0153 static const struct of_device_id pwm_imx1_dt_ids[] = {
0154     { .compatible = "fsl,imx1-pwm", },
0155     { /* sentinel */ }
0156 };
0157 MODULE_DEVICE_TABLE(of, pwm_imx1_dt_ids);
0158 
0159 static int pwm_imx1_probe(struct platform_device *pdev)
0160 {
0161     struct pwm_imx1_chip *imx;
0162 
0163     imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
0164     if (!imx)
0165         return -ENOMEM;
0166 
0167     imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
0168     if (IS_ERR(imx->clk_ipg))
0169         return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_ipg),
0170                      "getting ipg clock failed\n");
0171 
0172     imx->clk_per = devm_clk_get(&pdev->dev, "per");
0173     if (IS_ERR(imx->clk_per))
0174         return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_per),
0175                      "failed to get peripheral clock\n");
0176 
0177     imx->chip.ops = &pwm_imx1_ops;
0178     imx->chip.dev = &pdev->dev;
0179     imx->chip.npwm = 1;
0180 
0181     imx->mmio_base = devm_platform_ioremap_resource(pdev, 0);
0182     if (IS_ERR(imx->mmio_base))
0183         return PTR_ERR(imx->mmio_base);
0184 
0185     return devm_pwmchip_add(&pdev->dev, &imx->chip);
0186 }
0187 
0188 static struct platform_driver pwm_imx1_driver = {
0189     .driver = {
0190         .name = "pwm-imx1",
0191         .of_match_table = pwm_imx1_dt_ids,
0192     },
0193     .probe = pwm_imx1_probe,
0194 };
0195 module_platform_driver(pwm_imx1_driver);
0196 
0197 MODULE_LICENSE("GPL v2");
0198 MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");