| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- // 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, ®);
- 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, ®);
- 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");
|