xref: /kernel/linux/linux-5.10/drivers/pwm/pwm-pxa.c (revision 8c2ecf20)
18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * drivers/pwm/pwm-pxa.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * simple driver for PWM (Pulse Width Modulator) controller
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * 2008-02-13	initial version
88c2ecf20Sopenharmony_ci *		eric miao <eric.miao@marvell.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/kernel.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
148c2ecf20Sopenharmony_ci#include <linux/slab.h>
158c2ecf20Sopenharmony_ci#include <linux/err.h>
168c2ecf20Sopenharmony_ci#include <linux/clk.h>
178c2ecf20Sopenharmony_ci#include <linux/io.h>
188c2ecf20Sopenharmony_ci#include <linux/pwm.h>
198c2ecf20Sopenharmony_ci#include <linux/of_device.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <asm/div64.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define HAS_SECONDARY_PWM	0x10
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic const struct platform_device_id pwm_id_table[] = {
268c2ecf20Sopenharmony_ci	/*   PWM    has_secondary_pwm? */
278c2ecf20Sopenharmony_ci	{ "pxa25x-pwm", 0 },
288c2ecf20Sopenharmony_ci	{ "pxa27x-pwm", HAS_SECONDARY_PWM },
298c2ecf20Sopenharmony_ci	{ "pxa168-pwm", 0 },
308c2ecf20Sopenharmony_ci	{ "pxa910-pwm", 0 },
318c2ecf20Sopenharmony_ci	{ },
328c2ecf20Sopenharmony_ci};
338c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(platform, pwm_id_table);
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci/* PWM registers and bits definitions */
368c2ecf20Sopenharmony_ci#define PWMCR		(0x00)
378c2ecf20Sopenharmony_ci#define PWMDCR		(0x04)
388c2ecf20Sopenharmony_ci#define PWMPCR		(0x08)
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#define PWMCR_SD	(1 << 6)
418c2ecf20Sopenharmony_ci#define PWMDCR_FD	(1 << 10)
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistruct pxa_pwm_chip {
448c2ecf20Sopenharmony_ci	struct pwm_chip	chip;
458c2ecf20Sopenharmony_ci	struct device	*dev;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	struct clk	*clk;
488c2ecf20Sopenharmony_ci	void __iomem	*mmio_base;
498c2ecf20Sopenharmony_ci};
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic inline struct pxa_pwm_chip *to_pxa_pwm_chip(struct pwm_chip *chip)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	return container_of(chip, struct pxa_pwm_chip, chip);
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci/*
578c2ecf20Sopenharmony_ci * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
588c2ecf20Sopenharmony_ci * duty_ns   = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
598c2ecf20Sopenharmony_ci */
608c2ecf20Sopenharmony_cistatic int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
618c2ecf20Sopenharmony_ci			  int duty_ns, int period_ns)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip);
648c2ecf20Sopenharmony_ci	unsigned long long c;
658c2ecf20Sopenharmony_ci	unsigned long period_cycles, prescale, pv, dc;
668c2ecf20Sopenharmony_ci	unsigned long offset;
678c2ecf20Sopenharmony_ci	int rc;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	offset = pwm->hwpwm ? 0x10 : 0;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	c = clk_get_rate(pc->clk);
728c2ecf20Sopenharmony_ci	c = c * period_ns;
738c2ecf20Sopenharmony_ci	do_div(c, 1000000000);
748c2ecf20Sopenharmony_ci	period_cycles = c;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	if (period_cycles < 1)
778c2ecf20Sopenharmony_ci		period_cycles = 1;
788c2ecf20Sopenharmony_ci	prescale = (period_cycles - 1) / 1024;
798c2ecf20Sopenharmony_ci	pv = period_cycles / (prescale + 1) - 1;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	if (prescale > 63)
828c2ecf20Sopenharmony_ci		return -EINVAL;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	if (duty_ns == period_ns)
858c2ecf20Sopenharmony_ci		dc = PWMDCR_FD;
868c2ecf20Sopenharmony_ci	else
878c2ecf20Sopenharmony_ci		dc = (pv + 1) * duty_ns / period_ns;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	/* NOTE: the clock to PWM has to be enabled first
908c2ecf20Sopenharmony_ci	 * before writing to the registers
918c2ecf20Sopenharmony_ci	 */
928c2ecf20Sopenharmony_ci	rc = clk_prepare_enable(pc->clk);
938c2ecf20Sopenharmony_ci	if (rc < 0)
948c2ecf20Sopenharmony_ci		return rc;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	writel(prescale, pc->mmio_base + offset + PWMCR);
978c2ecf20Sopenharmony_ci	writel(dc, pc->mmio_base + offset + PWMDCR);
988c2ecf20Sopenharmony_ci	writel(pv, pc->mmio_base + offset + PWMPCR);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	clk_disable_unprepare(pc->clk);
1018c2ecf20Sopenharmony_ci	return 0;
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_cistatic int pxa_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
1058c2ecf20Sopenharmony_ci{
1068c2ecf20Sopenharmony_ci	struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	return clk_prepare_enable(pc->clk);
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	clk_disable_unprepare(pc->clk);
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_cistatic const struct pwm_ops pxa_pwm_ops = {
1198c2ecf20Sopenharmony_ci	.config = pxa_pwm_config,
1208c2ecf20Sopenharmony_ci	.enable = pxa_pwm_enable,
1218c2ecf20Sopenharmony_ci	.disable = pxa_pwm_disable,
1228c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
1238c2ecf20Sopenharmony_ci};
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci#ifdef CONFIG_OF
1268c2ecf20Sopenharmony_ci/*
1278c2ecf20Sopenharmony_ci * Device tree users must create one device instance for each PWM channel.
1288c2ecf20Sopenharmony_ci * Hence we dispense with the HAS_SECONDARY_PWM and "tell" the original driver
1298c2ecf20Sopenharmony_ci * code that this is a single channel pxa25x-pwm.  Currently all devices are
1308c2ecf20Sopenharmony_ci * supported identically.
1318c2ecf20Sopenharmony_ci */
1328c2ecf20Sopenharmony_cistatic const struct of_device_id pwm_of_match[] = {
1338c2ecf20Sopenharmony_ci	{ .compatible = "marvell,pxa250-pwm", .data = &pwm_id_table[0]},
1348c2ecf20Sopenharmony_ci	{ .compatible = "marvell,pxa270-pwm", .data = &pwm_id_table[0]},
1358c2ecf20Sopenharmony_ci	{ .compatible = "marvell,pxa168-pwm", .data = &pwm_id_table[0]},
1368c2ecf20Sopenharmony_ci	{ .compatible = "marvell,pxa910-pwm", .data = &pwm_id_table[0]},
1378c2ecf20Sopenharmony_ci	{ }
1388c2ecf20Sopenharmony_ci};
1398c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, pwm_of_match);
1408c2ecf20Sopenharmony_ci#else
1418c2ecf20Sopenharmony_ci#define pwm_of_match NULL
1428c2ecf20Sopenharmony_ci#endif
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic const struct platform_device_id *pxa_pwm_get_id_dt(struct device *dev)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	const struct of_device_id *id = of_match_device(pwm_of_match, dev);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	return id ? id->data : NULL;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cistatic struct pwm_device *
1528c2ecf20Sopenharmony_cipxa_pwm_of_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
1538c2ecf20Sopenharmony_ci{
1548c2ecf20Sopenharmony_ci	struct pwm_device *pwm;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	pwm = pwm_request_from_chip(pc, 0, NULL);
1578c2ecf20Sopenharmony_ci	if (IS_ERR(pwm))
1588c2ecf20Sopenharmony_ci		return pwm;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	pwm->args.period = args->args[0];
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	return pwm;
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cistatic int pwm_probe(struct platform_device *pdev)
1668c2ecf20Sopenharmony_ci{
1678c2ecf20Sopenharmony_ci	const struct platform_device_id *id = platform_get_device_id(pdev);
1688c2ecf20Sopenharmony_ci	struct pxa_pwm_chip *pwm;
1698c2ecf20Sopenharmony_ci	struct resource *r;
1708c2ecf20Sopenharmony_ci	int ret = 0;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	if (IS_ENABLED(CONFIG_OF) && id == NULL)
1738c2ecf20Sopenharmony_ci		id = pxa_pwm_get_id_dt(&pdev->dev);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	if (id == NULL)
1768c2ecf20Sopenharmony_ci		return -EINVAL;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
1798c2ecf20Sopenharmony_ci	if (pwm == NULL)
1808c2ecf20Sopenharmony_ci		return -ENOMEM;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	pwm->clk = devm_clk_get(&pdev->dev, NULL);
1838c2ecf20Sopenharmony_ci	if (IS_ERR(pwm->clk))
1848c2ecf20Sopenharmony_ci		return PTR_ERR(pwm->clk);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	pwm->chip.dev = &pdev->dev;
1878c2ecf20Sopenharmony_ci	pwm->chip.ops = &pxa_pwm_ops;
1888c2ecf20Sopenharmony_ci	pwm->chip.base = -1;
1898c2ecf20Sopenharmony_ci	pwm->chip.npwm = (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	if (IS_ENABLED(CONFIG_OF)) {
1928c2ecf20Sopenharmony_ci		pwm->chip.of_xlate = pxa_pwm_of_xlate;
1938c2ecf20Sopenharmony_ci		pwm->chip.of_pwm_n_cells = 1;
1948c2ecf20Sopenharmony_ci	}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1978c2ecf20Sopenharmony_ci	pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r);
1988c2ecf20Sopenharmony_ci	if (IS_ERR(pwm->mmio_base))
1998c2ecf20Sopenharmony_ci		return PTR_ERR(pwm->mmio_base);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	ret = pwmchip_add(&pwm->chip);
2028c2ecf20Sopenharmony_ci	if (ret < 0) {
2038c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
2048c2ecf20Sopenharmony_ci		return ret;
2058c2ecf20Sopenharmony_ci	}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, pwm);
2088c2ecf20Sopenharmony_ci	return 0;
2098c2ecf20Sopenharmony_ci}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_cistatic int pwm_remove(struct platform_device *pdev)
2128c2ecf20Sopenharmony_ci{
2138c2ecf20Sopenharmony_ci	struct pxa_pwm_chip *chip;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	chip = platform_get_drvdata(pdev);
2168c2ecf20Sopenharmony_ci	if (chip == NULL)
2178c2ecf20Sopenharmony_ci		return -ENODEV;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	return pwmchip_remove(&chip->chip);
2208c2ecf20Sopenharmony_ci}
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_cistatic struct platform_driver pwm_driver = {
2238c2ecf20Sopenharmony_ci	.driver		= {
2248c2ecf20Sopenharmony_ci		.name	= "pxa25x-pwm",
2258c2ecf20Sopenharmony_ci		.of_match_table = pwm_of_match,
2268c2ecf20Sopenharmony_ci	},
2278c2ecf20Sopenharmony_ci	.probe		= pwm_probe,
2288c2ecf20Sopenharmony_ci	.remove		= pwm_remove,
2298c2ecf20Sopenharmony_ci	.id_table	= pwm_id_table,
2308c2ecf20Sopenharmony_ci};
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cimodule_platform_driver(pwm_driver);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
235