浏览代码

pwm: add pruss iep-based pwm driver

This adds a new pwm driver that supports the PRUSS IEP-based PWM. It
uses remoteporc framework to communicate with PRUSS.

A PRUSS has two pwmchips because of two IEP modules, and each pwmchip
has upto 12 PWMs.  This PRUSS PWM requires a firmware running on PRU
which uses IEP compare events to generate PWM output.

This driver implements two platform device drivers. One handles the
PRUSS PWM module calling remoteproc to initialize the PRU. The other one
is the pwmchip device driver to handle the two pwmchips.

PWM polarity inverse is not supported by the PRU firmware.

Acked-by: Vignesh Raghavendra <vigneshr@ti.com>
Signed-off-by: Bin Liu <b-liu@ti.com>
Signed-off-by: Sekhar Nori <nsekhar@ti.com>
Bin Liu 6 年之前
父节点
当前提交
663016125f
共有 3 个文件被更改,包括 547 次插入0 次删除
  1. 10 0
      drivers/pwm/Kconfig
  2. 1 0
      drivers/pwm/Makefile
  3. 536 0
      drivers/pwm/pwm-pru.c

+ 10 - 0
drivers/pwm/Kconfig

@@ -508,4 +508,14 @@ config PWM_ZX
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-zx.
 
+config  PWM_PRU
+	tristate "PRU-ICSS PWM support"
+	depends on PRU_REMOTEPROC && ARCH_K3 || COMPILE_TEST
+	help
+	  PWM driver support for the PRU-ICSS PWM controller found on TI
+	  SOCs.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-pru.
+
 endif

+ 1 - 0
drivers/pwm/Makefile

@@ -51,3 +51,4 @@ obj-$(CONFIG_PWM_TWL)		+= pwm-twl.o
 obj-$(CONFIG_PWM_TWL_LED)	+= pwm-twl-led.o
 obj-$(CONFIG_PWM_VT8500)	+= pwm-vt8500.o
 obj-$(CONFIG_PWM_ZX)		+= pwm-zx.o
+obj-$(CONFIG_PWM_PRU)		+= pwm-pru.o

+ 536 - 0
drivers/pwm/pwm-pru.c

@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PWM driver for PRU PWM controller
+ *
+ * Copyright (C) 2019 by Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Bin Liu <b-liu@ti.com>
+ */
+
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/pruss.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/remoteproc.h>
+
+#define PP_FW_MAGIC_NUMBER	0x4d575047	/* "GPWM" */
+#define PP_NUM_CHIPS		2
+#define PPC_NUM_PWMS		12
+/*
+ * PWM duty cycle and period thresholds in ns. PWM output is undefined
+ * if its duty cycle or period is out of the range. Valid settings:
+ *     period: 10us ~ 2sec
+ *     duty cycle: 400ns ~ (period - 400ns)
+ */
+#define PP_MIN_DUTY_NS		400
+#define PP_MIN_PERIOD_NS	10000
+#define PP_MAX_PERIOD_NS	2000000000	/* 2 sec */
+
+/* global registers */
+#define PP_FW_MAGIC		0x00
+#define PP_FW_VERSION		0x08
+#define PP_CTRL			0x14
+#define PP_STAT			0x18
+
+/* PP_CTRL bits */
+#define PP_CTRL_IEP_EN		BIT(0)
+
+#define PP_CHIP0_OFFSET		0x1c
+#define PP_CHIP1_OFFSET		0x68
+
+/* pwm chip register offsets */
+#define PPC_PWM_CFG		0x00
+#define PPC_PWM_EN		0x08
+#define PPC_PWM_PERIOD		0x0c	/* holds half of period_ns value */
+#define PPC_PWM_DC0		0x10
+#define PPC_PWM_DC(x)		(PPC_PWM_DC0 + ((x) << 2))
+
+/* PP_PWM_RECFG bits */
+#define PPC_PWM_CFG_DC0		BIT(2)
+#define PPC_PWM_CFG_DC_MASK	GENMASK(13, 2)
+
+#define PPC_PWM_CFG_COMMIT	1
+
+enum {
+	PWMEN_UPDATE,
+	PRD_UPDATE,
+
+	MAX_REGFIELDS
+};
+
+#define to_pru_pwmchip(c)	container_of((c), struct pru_pwmchip, chip)
+
+struct pru_pwmchip {
+	struct pwm_chip chip;
+	int period_owner;
+	spinlock_t period_lock;    /* lock to serialize pwm period access */
+	struct regmap *map;
+	struct regmap_field *pwmen_update;
+	struct regmap_field *period_update;
+};
+
+struct pru_pwm {
+	struct device *dev;
+	struct rproc *pru;
+	struct pruss *pruss;
+	int pru_id;
+	struct pruss_mem_region mem;
+	struct regmap *map;
+	struct regmap_field *fw_inited;
+};
+
+static const struct regmap_config ppc_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.max_register = PPC_PWM_DC(PPC_NUM_PWMS - 1),
+};
+
+static const struct reg_field ppc_regfields[MAX_REGFIELDS] = {
+	[PWMEN_UPDATE] = REG_FIELD(PPC_PWM_CFG, 0, 0),
+	[PRD_UPDATE]   = REG_FIELD(PPC_PWM_CFG, 1, 1),
+};
+
+static struct regmap_config pp_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.max_register = PP_STAT,
+};
+
+static const struct reg_field pp_regfield = REG_FIELD(PP_STAT, 3, 3);
+
+static int prupwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+			     int duty_ns, int period_ns)
+{
+	struct pru_pwmchip *ppc = to_pru_pwmchip(chip);
+	int duty;
+	int idx;
+	int ret = 0;
+
+	spin_lock(&ppc->period_lock);
+	if (period_ns < PP_MIN_PERIOD_NS || period_ns > PP_MAX_PERIOD_NS) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* check whether period has been set by another pwm */
+	if (period_ns != pwm_get_period(pwm) && ppc->period_owner != -1 &&
+	    ppc->period_owner != pwm->hwpwm) {
+		ret = -EACCES;
+		goto out;
+	}
+
+	if ((duty_ns && duty_ns < PP_MIN_DUTY_NS) ||
+	    duty_ns > period_ns - PP_MIN_DUTY_NS) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (period_ns != pwm_get_period(pwm)) {
+		for (idx = 0; idx < chip->npwm; idx++) {
+			/* skip current pwm device */
+			if (idx == pwm->hwpwm)
+				continue;
+			/*
+			 * the period is global to all pwms, so it cannot be
+			 * less than the duty cycle of any pwm.
+			 */
+			duty = pwm_get_duty_cycle(&chip->pwms[idx]);
+			if (period_ns >= duty)
+				continue;
+
+			dev_err(chip->dev, "Error: new period (%d) is less than pwm%d duty cycle (%d)\n",
+				period_ns, idx, duty);
+			ret = -EINVAL;
+			goto out;
+		}
+		ppc->period_owner = pwm->hwpwm;
+		kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
+
+		/* update the new period in pwm->state for all pwms */
+		for (idx = 0; idx < chip->npwm; idx++)
+			pwm_set_period(&chip->pwms[idx], period_ns);
+
+		/* the pwm period register holds half of the period value */
+		regmap_write(ppc->map, PPC_PWM_PERIOD, period_ns >> 1);
+		regmap_field_write(ppc->period_update, PPC_PWM_CFG_COMMIT);
+	}
+
+	regmap_write(ppc->map, PPC_PWM_DC(pwm->hwpwm), duty_ns);
+	regmap_update_bits(ppc->map, PPC_PWM_CFG, PPC_PWM_CFG_DC0 << pwm->hwpwm,
+			   PPC_PWM_CFG_DC0 << pwm->hwpwm);
+
+out:
+	spin_unlock(&ppc->period_lock);
+	return ret;
+}
+
+static int prupwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct pru_pwmchip *ppc = to_pru_pwmchip(chip);
+
+	regmap_update_bits(ppc->map, PPC_PWM_EN, BIT(pwm->hwpwm),
+			   BIT(pwm->hwpwm));
+	regmap_field_write(ppc->pwmen_update, PPC_PWM_CFG_COMMIT);
+
+	return 0;
+}
+
+static void prupwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct pru_pwmchip *ppc = to_pru_pwmchip(chip);
+
+	regmap_update_bits(ppc->map, PPC_PWM_EN, BIT(pwm->hwpwm), 0);
+	regmap_field_write(ppc->pwmen_update, PPC_PWM_CFG_COMMIT);
+}
+
+/* default period register value might not be 0, update it in pwm->state */
+static void prupwm_pwm_get_init_state(struct pwm_chip *chip,
+				      struct pwm_device *pwm,
+				      struct pwm_state *state)
+{
+	struct pru_pwmchip *ppc = to_pru_pwmchip(chip);
+	int period;
+
+	regmap_read(ppc->map, PPC_PWM_PERIOD, &period);
+	pwm_set_period(pwm, period << 1);
+}
+
+static void prupwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct pru_pwmchip *ppc = to_pru_pwmchip(chip);
+
+	/* set pwm duty cycle register to 0 */
+	regmap_write(ppc->map, PPC_PWM_DC(pwm->hwpwm), 0);
+	regmap_update_bits(ppc->map, PPC_PWM_CFG, PPC_PWM_CFG_DC0 << pwm->hwpwm,
+			   PPC_PWM_CFG_DC0 << pwm->hwpwm);
+
+	pwm_set_duty_cycle(pwm, 0);
+
+	spin_lock(&ppc->period_lock);
+	if (ppc->period_owner == pwm->hwpwm) {
+		ppc->period_owner = -1;
+		kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
+	}
+	spin_unlock(&ppc->period_lock);
+}
+
+static const struct pwm_ops prupwm_pwm_ops = {
+	.config = prupwm_pwm_config,
+	.enable = prupwm_pwm_enable,
+	.disable = prupwm_pwm_disable,
+	.get_state = prupwm_pwm_get_init_state,
+	.free = prupwm_pwm_free,
+	.owner = THIS_MODULE,
+};
+
+static const struct of_device_id pru_pwmchip_of_match[] = {
+	{ .compatible = "ti,pru-pwmchip", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, pru_pwmchip_of_match);
+
+#ifdef CONFIG_PWM_SYSFS
+static int prupwm_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	struct pwm_chip *chip = dev_get_drvdata(dev);
+	struct pru_pwmchip *ppc = to_pru_pwmchip(chip);
+	int ret;
+
+	ret = add_uevent_var(env, "PERIOD_OWNER=%d", ppc->period_owner);
+	return ret;
+}
+#endif
+
+static int pru_pwmchip_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct platform_device *parent = to_platform_device(dev->parent);
+	struct pru_pwm *pp = platform_get_drvdata(parent);
+	struct pru_pwmchip *ppc;
+	void __iomem *mbase;
+	int idx, ret;
+
+	ppc = devm_kzalloc(pp->dev, sizeof(*ppc), GFP_KERNEL);
+	if (!ppc)
+		return -ENOMEM;
+
+	ret = of_property_read_u32(dev->of_node, "reg", &idx);
+	if (ret || idx < 0 || idx >= PP_NUM_CHIPS)
+		return -EINVAL;
+
+	platform_set_drvdata(pdev, ppc);
+	spin_lock_init(&ppc->period_lock);
+	ppc->period_owner = -1;
+
+	mbase = pp->mem.va + (idx ? PP_CHIP1_OFFSET : PP_CHIP0_OFFSET);
+	ppc->map = devm_regmap_init_mmio(dev, mbase, &ppc_regmap_config);
+	if (IS_ERR(ppc->map)) {
+		dev_err(dev, "failed to init regmap (%d)\n", ret);
+		return PTR_ERR(ppc->map);
+	}
+
+	ppc->pwmen_update =
+		devm_regmap_field_alloc(dev, ppc->map,
+					ppc_regfields[PWMEN_UPDATE]);
+	if (IS_ERR(ppc->pwmen_update))
+		return PTR_ERR(ppc->pwmen_update);
+
+	ppc->period_update =
+		devm_regmap_field_alloc(dev, ppc->map,
+					ppc_regfields[PRD_UPDATE]);
+	if (IS_ERR(ppc->period_update))
+		return PTR_ERR(ppc->period_update);
+
+	ppc->chip.dev = dev;
+	ppc->chip.ops = &prupwm_pwm_ops;
+	ppc->chip.base = -1;
+	ppc->chip.npwm = PPC_NUM_PWMS;
+
+	/* set initial duty cycle register of all pwms to 0 */
+	for (idx = 0; idx < PPC_NUM_PWMS; idx++)
+		regmap_write(ppc->map, PPC_PWM_DC(idx), 0);
+
+	/* commit the duty cycle config changes */
+	regmap_write(ppc->map, PPC_PWM_CFG, PPC_PWM_CFG_DC_MASK);
+
+	ret = pwmchip_add(&ppc->chip);
+	if (ret)
+		dev_err(dev, "pwmchip_add() failed: %d\n", ret);
+
+#ifdef CONFIG_PWM_SYSFS
+	if (ppc->chip.sysfs_dev)
+		ppc->chip.sysfs_dev->class->dev_uevent = prupwm_uevent;
+#endif
+
+	return ret;
+}
+
+static int pru_pwmchip_remove(struct platform_device *pdev)
+{
+	struct pru_pwmchip *ppc = platform_get_drvdata(pdev);
+
+	return pwmchip_remove(&ppc->chip);
+}
+
+static struct platform_driver pru_pwmchip_driver = {
+	.driver = {
+		.name = "pru_pwmchip",
+		.of_match_table = pru_pwmchip_of_match,
+	},
+	.probe = pru_pwmchip_probe,
+	.remove = pru_pwmchip_remove,
+};
+
+static int prupwm_init_prufw(struct device_node *np, struct pru_pwm *pp)
+{
+	struct device *dev = pp->dev;
+	struct device_node *child;
+	enum pruss_mem mem_id;
+	u32 reg, id;
+	int ret = 0;
+
+	pp->pru = pru_rproc_get(np, 0);
+	if (IS_ERR(pp->pru)) {
+		ret = PTR_ERR(pp->pru);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get pru (%d)\n", ret);
+		return ret;
+	}
+
+	pp->pruss = pruss_get(pp->pru);
+	if (IS_ERR(pp->pruss)) {
+		ret = PTR_ERR(pp->pruss);
+		dev_err(dev, "failed to get pruss handle (%d)\n", ret);
+		goto put_pru;
+	}
+
+	pp->pru_id = pru_rproc_get_id(pp->pru);
+	if (pp->pru_id < 0) {
+		dev_err(dev, "failed to get pru id (%d)\n", pp->pru_id);
+		ret = -EINVAL;
+		goto put_pruss;
+	}
+
+	if (pp->pru_id > 1) {
+		dev_err(dev, "invalid pru id (%d)\n", pp->pru_id);
+		ret = -EINVAL;
+		goto put_pruss;
+	}
+
+	mem_id = pp->pru_id ? PRUSS_MEM_DRAM1 : PRUSS_MEM_DRAM0;
+	ret = pruss_request_mem_region(pp->pruss, mem_id, &pp->mem);
+	if (ret) {
+		dev_err(dev, "failed to get pruss mem region (%d)\n", ret);
+		goto put_pruss;
+	}
+
+	pp->map = devm_regmap_init_mmio(dev, pp->mem.va, &pp_regmap_config);
+	if (IS_ERR(pp->map)) {
+		ret = PTR_ERR(pp->map);
+		dev_err(dev, "failed to init register map (%d)\n", ret);
+		goto put_mem;
+	}
+
+	pp->fw_inited = devm_regmap_field_alloc(dev, pp->map, pp_regfield);
+	if (IS_ERR(pp->fw_inited)) {
+		ret = PTR_ERR(pp->fw_inited);
+		goto put_mem;
+	}
+
+	/* clear the mem region before firmware runs by rproc_boot() */
+	memset_io(pp->mem.va, 0, pp->mem.size);
+
+	ret = rproc_boot(pp->pru);
+	if (ret) {
+		dev_err(dev, "failed to boot pru (%d)\n", ret);
+		goto put_mem;
+	}
+
+	regmap_read(pp->map, PP_FW_MAGIC, &reg);
+	if (reg != PP_FW_MAGIC_NUMBER) {
+		dev_err(dev, "invalid firmware magic number\n");
+		ret = -EINVAL;
+		goto put_rproc;
+	}
+
+	regmap_read(pp->map, PP_FW_VERSION, &reg);
+	if (reg > 0x01000000) {
+		dev_err(dev, "unsupported firmware version(0x%x)\n", reg);
+		ret = -EINVAL;
+		goto put_rproc;
+	}
+
+	reg = 0;
+	for_each_available_child_of_node(np, child) {
+		ret = of_property_read_u32(child, "reg", &id);
+
+		if (ret || id >= PP_NUM_CHIPS) {
+			dev_err(dev, "invalid pwmchip id %d (%d)\n", id, ret);
+			ret = -EINVAL;
+			goto put_rproc;
+		}
+
+		reg |= 1 << (id + 1);
+	}
+
+	if (reg)
+		reg |= PP_CTRL_IEP_EN;
+	regmap_write(pp->map, PP_CTRL, reg);
+
+	/* check for firmware init completion, timeout in 100us */
+	ret = regmap_field_read_poll_timeout(pp->fw_inited, reg, reg, 0, 100);
+	if (ret == -ETIMEDOUT)
+		dev_err(dev, "failed to initialize firmware\n");
+	else if (!ret)
+		return 0;
+
+put_rproc:
+	rproc_shutdown(pp->pru);
+put_mem:
+	pruss_release_mem_region(pp->pruss, &pp->mem);
+put_pruss:
+	pruss_put(pp->pruss);
+put_pru:
+	pru_rproc_put(pp->pru);
+
+	return ret;
+}
+
+static int prupwm_exit_pruss(struct pru_pwm *pp)
+{
+	int ret;
+
+	rproc_shutdown(pp->pru);
+	ret = pruss_release_mem_region(pp->pruss, &pp->mem);
+	if (ret)
+		return ret;
+
+	pruss_put(pp->pruss);
+	pru_rproc_put(pp->pru);
+
+	return 0;
+}
+
+static const struct of_device_id prupwm_of_match[] = {
+	{ .compatible = "ti,pru-pwm", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, prupwm_of_match);
+
+static int prupwm_probe(struct platform_device *pdev)
+{
+	struct pru_pwm *pp;
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	if (!np)
+		return -ENODEV;	/* non-DT not supported */
+
+	pp = devm_kzalloc(dev, sizeof(*pp), GFP_KERNEL);
+	if (!pp)
+		return -ENOMEM;
+
+	pp->dev = dev;
+	ret = prupwm_init_prufw(np, pp);
+	if (ret < 0)
+		return -ENODEV;
+
+	platform_set_drvdata(pdev, pp);
+
+	ret = of_platform_populate(np, NULL, NULL, dev);
+	if (ret) {
+		dev_err(dev, "failed to create pwmchip\n");
+		prupwm_exit_pruss(pp);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int prupwm_remove(struct platform_device *pdev)
+{
+	struct pru_pwm *pp = platform_get_drvdata(pdev);
+
+	of_platform_depopulate(&pdev->dev);
+	return prupwm_exit_pruss(pp);
+}
+
+static struct platform_driver prupwm_driver = {
+	.driver = {
+		.name = "prupwm",
+		.of_match_table = prupwm_of_match,
+	},
+	.probe = prupwm_probe,
+	.remove = prupwm_remove,
+};
+
+static int __init prupwm_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&pru_pwmchip_driver);
+	if (ret)
+		return ret;
+
+	ret = platform_driver_register(&prupwm_driver);
+	if (ret)
+		platform_driver_unregister(&pru_pwmchip_driver);
+
+	return ret;
+}
+module_init(prupwm_init);
+
+static void __exit prupwm_exit(void)
+{
+	platform_driver_unregister(&prupwm_driver);
+	platform_driver_unregister(&pru_pwmchip_driver);
+}
+module_exit(prupwm_exit);
+
+MODULE_AUTHOR("Bin Liu <b-liu@ti.com>");
+MODULE_DESCRIPTION("PRU PWM Driver");
+MODULE_LICENSE("GPL v2");