334 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * ECAP PWM driver4 *5 * Copyright (C) 2012 Texas Instruments, Inc. - https://www.ti.com/6 */7 8#include <linux/module.h>9#include <linux/platform_device.h>10#include <linux/io.h>11#include <linux/err.h>12#include <linux/clk.h>13#include <linux/pm_runtime.h>14#include <linux/pwm.h>15#include <linux/of.h>16 17/* ECAP registers and bits definitions */18#define CAP1 0x0819#define CAP2 0x0C20#define CAP3 0x1021#define CAP4 0x1422#define ECCTL2 0x2A23#define ECCTL2_APWM_POL_LOW BIT(10)24#define ECCTL2_APWM_MODE BIT(9)25#define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6))26#define ECCTL2_TSCTR_FREERUN BIT(4)27 28struct ecap_context {29 u32 cap3;30 u32 cap4;31 u16 ecctl2;32};33 34struct ecap_pwm_chip {35 unsigned int clk_rate;36 void __iomem *mmio_base;37 struct ecap_context ctx;38};39 40static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip)41{42 return pwmchip_get_drvdata(chip);43}44 45/*46 * period_ns = 10^9 * period_cycles / PWM_CLK_RATE47 * duty_ns = 10^9 * duty_cycles / PWM_CLK_RATE48 */49static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,50 int duty_ns, int period_ns, int enabled)51{52 struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);53 u32 period_cycles, duty_cycles;54 unsigned long long c;55 u16 value;56 57 c = pc->clk_rate;58 c = c * period_ns;59 do_div(c, NSEC_PER_SEC);60 period_cycles = (u32)c;61 62 if (period_cycles < 1) {63 period_cycles = 1;64 duty_cycles = 1;65 } else {66 c = pc->clk_rate;67 c = c * duty_ns;68 do_div(c, NSEC_PER_SEC);69 duty_cycles = (u32)c;70 }71 72 pm_runtime_get_sync(pwmchip_parent(chip));73 74 value = readw(pc->mmio_base + ECCTL2);75 76 /* Configure APWM mode & disable sync option */77 value |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA;78 79 writew(value, pc->mmio_base + ECCTL2);80 81 if (!enabled) {82 /* Update active registers if not running */83 writel(duty_cycles, pc->mmio_base + CAP2);84 writel(period_cycles, pc->mmio_base + CAP1);85 } else {86 /*87 * Update shadow registers to configure period and88 * compare values. This helps current PWM period to89 * complete on reconfiguring90 */91 writel(duty_cycles, pc->mmio_base + CAP4);92 writel(period_cycles, pc->mmio_base + CAP3);93 }94 95 if (!enabled) {96 value = readw(pc->mmio_base + ECCTL2);97 /* Disable APWM mode to put APWM output Low */98 value &= ~ECCTL2_APWM_MODE;99 writew(value, pc->mmio_base + ECCTL2);100 }101 102 pm_runtime_put_sync(pwmchip_parent(chip));103 104 return 0;105}106 107static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,108 enum pwm_polarity polarity)109{110 struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);111 u16 value;112 113 pm_runtime_get_sync(pwmchip_parent(chip));114 115 value = readw(pc->mmio_base + ECCTL2);116 117 if (polarity == PWM_POLARITY_INVERSED)118 /* Duty cycle defines LOW period of PWM */119 value |= ECCTL2_APWM_POL_LOW;120 else121 /* Duty cycle defines HIGH period of PWM */122 value &= ~ECCTL2_APWM_POL_LOW;123 124 writew(value, pc->mmio_base + ECCTL2);125 126 pm_runtime_put_sync(pwmchip_parent(chip));127 128 return 0;129}130 131static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)132{133 struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);134 u16 value;135 136 /* Leave clock enabled on enabling PWM */137 pm_runtime_get_sync(pwmchip_parent(chip));138 139 /*140 * Enable 'Free run Time stamp counter mode' to start counter141 * and 'APWM mode' to enable APWM output142 */143 value = readw(pc->mmio_base + ECCTL2);144 value |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE;145 writew(value, pc->mmio_base + ECCTL2);146 147 return 0;148}149 150static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)151{152 struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);153 u16 value;154 155 /*156 * Disable 'Free run Time stamp counter mode' to stop counter157 * and 'APWM mode' to put APWM output to low158 */159 value = readw(pc->mmio_base + ECCTL2);160 value &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE);161 writew(value, pc->mmio_base + ECCTL2);162 163 /* Disable clock on PWM disable */164 pm_runtime_put_sync(pwmchip_parent(chip));165}166 167static int ecap_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,168 const struct pwm_state *state)169{170 int err;171 int enabled = pwm->state.enabled;172 173 if (state->polarity != pwm->state.polarity) {174 175 if (enabled) {176 ecap_pwm_disable(chip, pwm);177 enabled = false;178 }179 180 err = ecap_pwm_set_polarity(chip, pwm, state->polarity);181 if (err)182 return err;183 }184 185 if (!state->enabled) {186 if (enabled)187 ecap_pwm_disable(chip, pwm);188 return 0;189 }190 191 if (state->period > NSEC_PER_SEC)192 return -ERANGE;193 194 err = ecap_pwm_config(chip, pwm, state->duty_cycle,195 state->period, enabled);196 if (err)197 return err;198 199 if (!enabled)200 return ecap_pwm_enable(chip, pwm);201 202 return 0;203}204 205static const struct pwm_ops ecap_pwm_ops = {206 .apply = ecap_pwm_apply,207};208 209static const struct of_device_id ecap_of_match[] = {210 { .compatible = "ti,am3352-ecap" },211 { .compatible = "ti,am33xx-ecap" },212 {},213};214MODULE_DEVICE_TABLE(of, ecap_of_match);215 216static int ecap_pwm_probe(struct platform_device *pdev)217{218 struct device_node *np = pdev->dev.of_node;219 struct ecap_pwm_chip *pc;220 struct pwm_chip *chip;221 struct clk *clk;222 int ret;223 224 chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pc));225 if (IS_ERR(chip))226 return PTR_ERR(chip);227 pc = to_ecap_pwm_chip(chip);228 229 clk = devm_clk_get(&pdev->dev, "fck");230 if (IS_ERR(clk)) {231 if (of_device_is_compatible(np, "ti,am33xx-ecap")) {232 dev_warn(&pdev->dev, "Binding is obsolete.\n");233 clk = devm_clk_get(pdev->dev.parent, "fck");234 }235 }236 237 if (IS_ERR(clk)) {238 dev_err(&pdev->dev, "failed to get clock\n");239 return PTR_ERR(clk);240 }241 242 pc->clk_rate = clk_get_rate(clk);243 if (!pc->clk_rate) {244 dev_err(&pdev->dev, "failed to get clock rate\n");245 return -EINVAL;246 }247 248 chip->ops = &ecap_pwm_ops;249 250 pc->mmio_base = devm_platform_ioremap_resource(pdev, 0);251 if (IS_ERR(pc->mmio_base))252 return PTR_ERR(pc->mmio_base);253 254 ret = devm_pwmchip_add(&pdev->dev, chip);255 if (ret < 0) {256 dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);257 return ret;258 }259 260 platform_set_drvdata(pdev, chip);261 pm_runtime_enable(&pdev->dev);262 263 return 0;264}265 266static void ecap_pwm_remove(struct platform_device *pdev)267{268 pm_runtime_disable(&pdev->dev);269}270 271static void ecap_pwm_save_context(struct pwm_chip *chip)272{273 struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);274 275 pm_runtime_get_sync(pwmchip_parent(chip));276 pc->ctx.ecctl2 = readw(pc->mmio_base + ECCTL2);277 pc->ctx.cap4 = readl(pc->mmio_base + CAP4);278 pc->ctx.cap3 = readl(pc->mmio_base + CAP3);279 pm_runtime_put_sync(pwmchip_parent(chip));280}281 282static void ecap_pwm_restore_context(struct pwm_chip *chip)283{284 struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);285 286 writel(pc->ctx.cap3, pc->mmio_base + CAP3);287 writel(pc->ctx.cap4, pc->mmio_base + CAP4);288 writew(pc->ctx.ecctl2, pc->mmio_base + ECCTL2);289}290 291static int ecap_pwm_suspend(struct device *dev)292{293 struct pwm_chip *chip = dev_get_drvdata(dev);294 struct pwm_device *pwm = chip->pwms;295 296 ecap_pwm_save_context(chip);297 298 /* Disable explicitly if PWM is running */299 if (pwm_is_enabled(pwm))300 pm_runtime_put_sync(dev);301 302 return 0;303}304 305static int ecap_pwm_resume(struct device *dev)306{307 struct pwm_chip *chip = dev_get_drvdata(dev);308 struct pwm_device *pwm = chip->pwms;309 310 /* Enable explicitly if PWM was running */311 if (pwm_is_enabled(pwm))312 pm_runtime_get_sync(dev);313 314 ecap_pwm_restore_context(chip);315 return 0;316}317 318static DEFINE_SIMPLE_DEV_PM_OPS(ecap_pwm_pm_ops, ecap_pwm_suspend, ecap_pwm_resume);319 320static struct platform_driver ecap_pwm_driver = {321 .driver = {322 .name = "ecap",323 .of_match_table = ecap_of_match,324 .pm = pm_ptr(&ecap_pwm_pm_ops),325 },326 .probe = ecap_pwm_probe,327 .remove = ecap_pwm_remove,328};329module_platform_driver(ecap_pwm_driver);330 331MODULE_DESCRIPTION("ECAP PWM driver");332MODULE_AUTHOR("Texas Instruments");333MODULE_LICENSE("GPL");334