brintos

brintos / linux-shallow public Read only

0
0
Text · 7.5 KiB · d91b2bd Raw
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