diff options
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 23 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 3 | ||||
-rw-r--r-- | drivers/pwm/core.c | 29 | ||||
-rw-r--r-- | drivers/pwm/pwm-atmel-tcb.c | 5 | ||||
-rw-r--r-- | drivers/pwm/pwm-bfin.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-imx.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpc32xx.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-mxs.c | 7 | ||||
-rw-r--r-- | drivers/pwm/pwm-pca9685.c | 300 | ||||
-rw-r--r-- | drivers/pwm/pwm-puv3.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-renesas-tpu.c | 474 | ||||
-rw-r--r-- | drivers/pwm/pwm-spear.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-tegra.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-tiehrpwm.c | 13 | ||||
-rw-r--r-- | drivers/pwm/sysfs.c | 352 |
15 files changed, 1198 insertions, 14 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 115b64453493..75840b5cea6d 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -28,6 +28,10 @@ menuconfig PWM if PWM +config PWM_SYSFS + bool + default y if SYSFS + config PWM_AB8500 tristate "AB8500 PWM support" depends on AB8500_CORE && ARCH_U8500 @@ -97,6 +101,15 @@ config PWM_MXS To compile this driver as a module, choose M here: the module will be called pwm-mxs. +config PWM_PCA9685 + tristate "NXP PCA9685 PWM driver" + depends on OF && REGMAP_I2C + help + Generic PWM framework driver for NXP PCA9685 LED controller. + + To compile this driver as a module, choose M here: the module + will be called pwm-pca9685. + config PWM_PUV3 tristate "PKUnity NetBook-0916 PWM support" depends on ARCH_PUV3 @@ -115,6 +128,16 @@ config PWM_PXA To compile this driver as a module, choose M here: the module will be called pwm-pxa. +config PWM_RENESAS_TPU + tristate "Renesas TPU PWM support" + depends on ARCH_SHMOBILE + help + This driver exposes the Timer Pulse Unit (TPU) PWM controller found + in Renesas chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-renesas-tpu. + config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 94ba21e24bd6..77a8c185c5b2 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_PWM) += core.o +obj-$(CONFIG_PWM_SYSFS) += sysfs.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o @@ -6,8 +7,10 @@ obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o +obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o +obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 32221cb0cbe7..dfbfbc521768 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -274,6 +274,8 @@ int pwmchip_add(struct pwm_chip *chip) if (IS_ENABLED(CONFIG_OF)) of_pwmchip_add(chip); + pwmchip_sysfs_export(chip); + out: mutex_unlock(&pwm_lock); return ret; @@ -310,6 +312,8 @@ int pwmchip_remove(struct pwm_chip *chip) free_pwms(chip); + pwmchip_sysfs_unexport(chip); + out: mutex_unlock(&pwm_lock); return ret; @@ -402,10 +406,19 @@ EXPORT_SYMBOL_GPL(pwm_free); */ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) { + int err; + if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns) return -EINVAL; - return pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); + err = pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); + if (err) + return err; + + pwm->duty_cycle = duty_ns; + pwm->period = period_ns; + + return 0; } EXPORT_SYMBOL_GPL(pwm_config); @@ -418,6 +431,8 @@ EXPORT_SYMBOL_GPL(pwm_config); */ int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) { + int err; + if (!pwm || !pwm->chip->ops) return -EINVAL; @@ -427,7 +442,13 @@ int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) if (test_bit(PWMF_ENABLED, &pwm->flags)) return -EBUSY; - return pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity); + err = pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity); + if (err) + return err; + + pwm->polarity = polarity; + + return 0; } EXPORT_SYMBOL_GPL(pwm_set_polarity); @@ -694,7 +715,7 @@ struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id) { struct pwm_device **ptr, *pwm; - ptr = devres_alloc(devm_pwm_release, sizeof(**ptr), GFP_KERNEL); + ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) return ERR_PTR(-ENOMEM); @@ -724,7 +745,7 @@ struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np, { struct pwm_device **ptr, *pwm; - ptr = devres_alloc(devm_pwm_release, sizeof(**ptr), GFP_KERNEL); + ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) return ERR_PTR(-ENOMEM); diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c index 0a7b6582edb1..ba6ce01035e4 100644 --- a/drivers/pwm/pwm-atmel-tcb.c +++ b/drivers/pwm/pwm-atmel-tcb.c @@ -76,7 +76,7 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip, if (!tcbpwm) return -ENOMEM; - ret = clk_enable(tc->clk[group]); + ret = clk_prepare_enable(tc->clk[group]); if (ret) { devm_kfree(chip->dev, tcbpwm); return ret; @@ -124,7 +124,7 @@ static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); struct atmel_tc *tc = tcbpwmc->tc; - clk_disable(tc->clk[pwm->hwpwm / 2]); + clk_disable_unprepare(tc->clk[pwm->hwpwm / 2]); tcbpwmc->pwms[pwm->hwpwm] = NULL; devm_kfree(chip->dev, tcbpwm); } @@ -434,6 +434,7 @@ MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids); static struct platform_driver atmel_tcb_pwm_driver = { .driver = { .name = "atmel-tcb-pwm", + .owner = THIS_MODULE, .of_match_table = atmel_tcb_pwm_dt_ids, }, .probe = atmel_tcb_pwm_probe, diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c index 7631ef194de7..9985d830e554 100644 --- a/drivers/pwm/pwm-bfin.c +++ b/drivers/pwm/pwm-bfin.c @@ -149,6 +149,7 @@ static int bfin_pwm_remove(struct platform_device *pdev) static struct platform_driver bfin_pwm_driver = { .driver = { .name = "bfin-pwm", + .owner = THIS_MODULE, }, .probe = bfin_pwm_probe, .remove = bfin_pwm_remove, diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index c938bae18812..2b7c4f88b461 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -295,6 +295,7 @@ static int imx_pwm_remove(struct platform_device *pdev) static struct platform_driver imx_pwm_driver = { .driver = { .name = "imx-pwm", + .owner = THIS_MODULE, .of_match_table = of_match_ptr(imx_pwm_dt_ids), }, .probe = imx_pwm_probe, diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c index 8272883c0d05..efb6c7bf8750 100644 --- a/drivers/pwm/pwm-lpc32xx.c +++ b/drivers/pwm/pwm-lpc32xx.c @@ -171,6 +171,7 @@ MODULE_DEVICE_TABLE(of, lpc32xx_pwm_dt_ids); static struct platform_driver lpc32xx_pwm_driver = { .driver = { .name = "lpc32xx-pwm", + .owner = THIS_MODULE, .of_match_table = of_match_ptr(lpc32xx_pwm_dt_ids), }, .probe = lpc32xx_pwm_probe, diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c index 3febdddf71f9..2c77b81da7c4 100644 --- a/drivers/pwm/pwm-mxs.c +++ b/drivers/pwm/pwm-mxs.c @@ -16,7 +16,6 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> -#include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/slab.h> @@ -130,7 +129,6 @@ static int mxs_pwm_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; struct mxs_pwm_chip *mxs; struct resource *res; - struct pinctrl *pinctrl; int ret; mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL); @@ -142,10 +140,6 @@ static int mxs_pwm_probe(struct platform_device *pdev) if (IS_ERR(mxs->base)) return PTR_ERR(mxs->base); - pinctrl = devm_pinctrl_get_select_default(&pdev->dev); - if (IS_ERR(pinctrl)) - return PTR_ERR(pinctrl); - mxs->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(mxs->clk)) return PTR_ERR(mxs->clk); @@ -188,6 +182,7 @@ MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids); static struct platform_driver mxs_pwm_driver = { .driver = { .name = "mxs-pwm", + .owner = THIS_MODULE, .of_match_table = of_match_ptr(mxs_pwm_dt_ids), }, .probe = mxs_pwm_probe, diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c new file mode 100644 index 000000000000..3fb775ded0df --- /dev/null +++ b/drivers/pwm/pwm-pca9685.c @@ -0,0 +1,300 @@ +/* + * Driver for PCA9685 16-channel 12-bit PWM LED controller + * + * Copyright (C) 2013 Steffen Trumtrar <s.trumtrar@pengutronix.de> + * + * based on the pwm-twl-led.c driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define PCA9685_MODE1 0x00 +#define PCA9685_MODE2 0x01 +#define PCA9685_SUBADDR1 0x02 +#define PCA9685_SUBADDR2 0x03 +#define PCA9685_SUBADDR3 0x04 +#define PCA9685_ALLCALLADDR 0x05 +#define PCA9685_LEDX_ON_L 0x06 +#define PCA9685_LEDX_ON_H 0x07 +#define PCA9685_LEDX_OFF_L 0x08 +#define PCA9685_LEDX_OFF_H 0x09 + +#define PCA9685_ALL_LED_ON_L 0xFA +#define PCA9685_ALL_LED_ON_H 0xFB +#define PCA9685_ALL_LED_OFF_L 0xFC +#define PCA9685_ALL_LED_OFF_H 0xFD +#define PCA9685_PRESCALE 0xFE + +#define PCA9685_NUMREGS 0xFF +#define PCA9685_MAXCHAN 0x10 + +#define LED_FULL (1 << 4) +#define MODE1_SLEEP (1 << 4) +#define MODE2_INVRT (1 << 4) +#define MODE2_OUTDRV (1 << 2) + +#define LED_N_ON_H(N) (PCA9685_LEDX_ON_H + (4 * (N))) +#define LED_N_ON_L(N) (PCA9685_LEDX_ON_L + (4 * (N))) +#define LED_N_OFF_H(N) (PCA9685_LEDX_OFF_H + (4 * (N))) +#define LED_N_OFF_L(N) (PCA9685_LEDX_OFF_L + (4 * (N))) + +struct pca9685 { + struct pwm_chip chip; + struct regmap *regmap; + int active_cnt; +}; + +static inline struct pca9685 *to_pca(struct pwm_chip *chip) +{ + return container_of(chip, struct pca9685, chip); +} + +static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pca9685 *pca = to_pca(chip); + unsigned long long duty; + unsigned int reg; + + if (duty_ns < 1) { + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, LED_FULL); + + return 0; + } + + if (duty_ns == period_ns) { + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_ON_H; + else + reg = LED_N_ON_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, LED_FULL); + + return 0; + } + + duty = 4096 * (unsigned long long)duty_ns; + duty = DIV_ROUND_UP_ULL(duty, period_ns); + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_L; + else + reg = LED_N_OFF_L(pwm->hwpwm); + + regmap_write(pca->regmap, reg, (int)duty & 0xff); + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf); + + return 0; +} + +static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = to_pca(chip); + unsigned int reg; + + /* + * The PWM subsystem does not support a pre-delay. + * So, set the ON-timeout to 0 + */ + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_ON_L; + else + reg = LED_N_ON_L(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0); + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_ON_H; + else + reg = LED_N_ON_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0); + + /* + * Clear the full-off bit. + * It has precedence over the others and must be off. + */ + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0); + + return 0; +} + +static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = to_pca(chip); + unsigned int reg; + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, LED_FULL); + + /* Clear the LED_OFF counter. */ + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_L; + else + reg = LED_N_OFF_L(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0x0); +} + +static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = to_pca(chip); + + if (pca->active_cnt++ == 0) + return regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_SLEEP, 0x0); + + return 0; +} + +static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = to_pca(chip); + + if (--pca->active_cnt == 0) + regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP, + MODE1_SLEEP); +} + +static const struct pwm_ops pca9685_pwm_ops = { + .enable = pca9685_pwm_enable, + .disable = pca9685_pwm_disable, + .config = pca9685_pwm_config, + .request = pca9685_pwm_request, + .free = pca9685_pwm_free, + .owner = THIS_MODULE, +}; + +static struct regmap_config pca9685_regmap_i2c_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PCA9685_NUMREGS, + .cache_type = REGCACHE_NONE, +}; + +static int pca9685_pwm_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *np = client->dev.of_node; + struct pca9685 *pca; + int ret; + int mode2; + + pca = devm_kzalloc(&client->dev, sizeof(*pca), GFP_KERNEL); + if (!pca) + return -ENOMEM; + + pca->regmap = devm_regmap_init_i2c(client, &pca9685_regmap_i2c_config); + if (IS_ERR(pca->regmap)) { + ret = PTR_ERR(pca->regmap); + dev_err(&client->dev, "Failed to initialize register map: %d\n", + ret); + return ret; + } + + i2c_set_clientdata(client, pca); + + regmap_read(pca->regmap, PCA9685_MODE2, &mode2); + + if (of_property_read_bool(np, "invert")) + mode2 |= MODE2_INVRT; + else + mode2 &= ~MODE2_INVRT; + + if (of_property_read_bool(np, "open-drain")) + mode2 &= ~MODE2_OUTDRV; + else + mode2 |= MODE2_OUTDRV; + + regmap_write(pca->regmap, PCA9685_MODE2, mode2); + + /* clear all "full off" bits */ + regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0); + regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, 0); + + pca->chip.ops = &pca9685_pwm_ops; + /* add an extra channel for ALL_LED */ + pca->chip.npwm = PCA9685_MAXCHAN + 1; + + pca->chip.dev = &client->dev; + pca->chip.base = -1; + pca->chip.can_sleep = true; + + return pwmchip_add(&pca->chip); +} + +static int pca9685_pwm_remove(struct i2c_client *client) +{ + struct pca9685 *pca = i2c_get_clientdata(client); + + regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP, + MODE1_SLEEP); + + return pwmchip_remove(&pca->chip); +} + +static const struct i2c_device_id pca9685_id[] = { + { "pca9685", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, pca9685_id); + +static const struct of_device_id pca9685_dt_ids[] = { + { .compatible = "nxp,pca9685-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pca9685_dt_ids); + +static struct i2c_driver pca9685_i2c_driver = { + .driver = { + .name = "pca9685-pwm", + .owner = THIS_MODULE, + .of_match_table = pca9685_dt_ids, + }, + .probe = pca9685_pwm_probe, + .remove = pca9685_pwm_remove, + .id_table = pca9685_id, +}; + +module_i2c_driver(pca9685_i2c_driver); + +MODULE_AUTHOR("Steffen Trumtrar <s.trumtrar@pengutronix.de>"); +MODULE_DESCRIPTION("PWM driver for PCA9685"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-puv3.c b/drivers/pwm/pwm-puv3.c index ed6007b27585..a9a28083f245 100644 --- a/drivers/pwm/pwm-puv3.c +++ b/drivers/pwm/pwm-puv3.c @@ -146,6 +146,7 @@ static int pwm_remove(struct platform_device *pdev) static struct platform_driver puv3_pwm_driver = { .driver = { .name = "PKUnity-v3-PWM", + .owner = THIS_MODULE, }, .probe = pwm_probe, .remove = pwm_remove, diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c new file mode 100644 index 000000000000..2600892782c1 --- /dev/null +++ b/drivers/pwm/pwm-renesas-tpu.c @@ -0,0 +1,474 @@ +/* + * R-Mobile TPU PWM driver + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_data/pwm-renesas-tpu.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define TPU_TSTR 0x00 /* Timer start register (shared) */ + +#define TPU_TCRn 0x00 /* Timer control register */ +#define TPU_TCR_CCLR_NONE (0 << 5) +#define TPU_TCR_CCLR_TGRA (1 << 5) +#define TPU_TCR_CCLR_TGRB (2 << 5) +#define TPU_TCR_CCLR_TGRC (5 << 5) +#define TPU_TCR_CCLR_TGRD (6 << 5) +#define TPU_TCR_CKEG_RISING (0 << 3) +#define TPU_TCR_CKEG_FALLING (1 << 3) +#define TPU_TCR_CKEG_BOTH (2 << 3) +#define TPU_TMDRn 0x04 /* Timer mode register */ +#define TPU_TMDR_BFWT (1 << 6) +#define TPU_TMDR_BFB (1 << 5) +#define TPU_TMDR_BFA (1 << 4) +#define TPU_TMDR_MD_NORMAL (0 << 0) +#define TPU_TMDR_MD_PWM (2 << 0) +#define TPU_TIORn 0x08 /* Timer I/O control register */ +#define TPU_TIOR_IOA_0 (0 << 0) +#define TPU_TIOR_IOA_0_CLR (1 << 0) +#define TPU_TIOR_IOA_0_SET (2 << 0) +#define TPU_TIOR_IOA_0_TOGGLE (3 << 0) +#define TPU_TIOR_IOA_1 (4 << 0) +#define TPU_TIOR_IOA_1_CLR (5 << 0) +#define TPU_TIOR_IOA_1_SET (6 << 0) +#define TPU_TIOR_IOA_1_TOGGLE (7 << 0) +#define TPU_TIERn 0x0c /* Timer interrupt enable register */ +#define TPU_TSRn 0x10 /* Timer status register */ +#define TPU_TCNTn 0x14 /* Timer counter */ +#define TPU_TGRAn 0x18 /* Timer general register A */ +#define TPU_TGRBn 0x1c /* Timer general register B */ +#define TPU_TGRCn 0x20 /* Timer general register C */ +#define TPU_TGRDn 0x24 /* Timer general register D */ + +#define TPU_CHANNEL_OFFSET 0x10 +#define TPU_CHANNEL_SIZE 0x40 + +enum tpu_pin_state { + TPU_PIN_INACTIVE, /* Pin is driven inactive */ + TPU_PIN_PWM, /* Pin is driven by PWM */ + TPU_PIN_ACTIVE, /* Pin is driven active */ +}; + +struct tpu_device; + +struct tpu_pwm_device { + bool timer_on; /* Whether the timer is running */ + + struct tpu_device *tpu; + unsigned int channel; /* Channel number in the TPU */ + + enum pwm_polarity polarity; + unsigned int prescaler; + u16 period; + u16 duty; +}; + +struct tpu_device { + struct platform_device *pdev; + struct tpu_pwm_platform_data *pdata; + struct pwm_chip chip; + spinlock_t lock; + + void __iomem *base; + struct clk *clk; +}; + +#define to_tpu_device(c) container_of(c, struct tpu_device, chip) + +static void tpu_pwm_write(struct tpu_pwm_device *pwm, int reg_nr, u16 value) +{ + void __iomem *base = pwm->tpu->base + TPU_CHANNEL_OFFSET + + pwm->channel * TPU_CHANNEL_SIZE; + + iowrite16(value, base + reg_nr); +} + +static void tpu_pwm_set_pin(struct tpu_pwm_device *pwm, + enum tpu_pin_state state) +{ + static const char * const states[] = { "inactive", "PWM", "active" }; + + dev_dbg(&pwm->tpu->pdev->dev, "%u: configuring pin as %s\n", + pwm->channel, states[state]); + + switch (state) { + case TPU_PIN_INACTIVE: + tpu_pwm_write(pwm, TPU_TIORn, + pwm->polarity == PWM_POLARITY_INVERSED ? + TPU_TIOR_IOA_1 : TPU_TIOR_IOA_0); + break; + case TPU_PIN_PWM: + tpu_pwm_write(pwm, TPU_TIORn, + pwm->polarity == PWM_POLARITY_INVERSED ? + TPU_TIOR_IOA_0_SET : TPU_TIOR_IOA_1_CLR); + break; + case TPU_PIN_ACTIVE: + tpu_pwm_write(pwm, TPU_TIORn, + pwm->polarity == PWM_POLARITY_INVERSED ? + TPU_TIOR_IOA_0 : TPU_TIOR_IOA_1); + break; + } +} + +static void tpu_pwm_start_stop(struct tpu_pwm_device *pwm, int start) +{ + unsigned long flags; + u16 value; + + spin_lock_irqsave(&pwm->tpu->lock, flags); + value = ioread16(pwm->tpu->base + TPU_TSTR); + + if (start) + value |= 1 << pwm->channel; + else + value &= ~(1 << pwm->channel); + + iowrite16(value, pwm->tpu->base + TPU_TSTR); + spin_unlock_irqrestore(&pwm->tpu->lock, flags); +} + +static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm) +{ + int ret; + + if (!pwm->timer_on) { + /* Wake up device and enable clock. */ + pm_runtime_get_sync(&pwm->tpu->pdev->dev); + ret = clk_prepare_enable(pwm->tpu->clk); + if (ret) { + dev_err(&pwm->tpu->pdev->dev, "cannot enable clock\n"); + return ret; + } + pwm->timer_on = true; + } + + /* + * Make sure the channel is stopped, as we need to reconfigure it + * completely. First drive the pin to the inactive state to avoid + * glitches. + */ + tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE); + tpu_pwm_start_stop(pwm, false); + + /* + * - Clear TCNT on TGRB match + * - Count on rising edge + * - Set prescaler + * - Output 0 until TGRA, output 1 until TGRB (active low polarity) + * - Output 1 until TGRA, output 0 until TGRB (active high polarity + * - PWM mode + */ + tpu_pwm_write(pwm, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING | + pwm->prescaler); + tpu_pwm_write(pwm, TPU_TMDRn, TPU_TMDR_MD_PWM); + tpu_pwm_set_pin(pwm, TPU_PIN_PWM); + tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty); + tpu_pwm_write(pwm, TPU_TGRBn, pwm->period); + + dev_dbg(&pwm->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n", + pwm->channel, pwm->duty, pwm->period); + + /* Start the channel. */ + tpu_pwm_start_stop(pwm, true); + + return 0; +} + +static void tpu_pwm_timer_stop(struct tpu_pwm_device *pwm) +{ + if (!pwm->timer_on) + return; + + /* Disable channel. */ + tpu_pwm_start_stop(pwm, false); + + /* Stop clock and mark device as idle. */ + clk_disable_unprepare(pwm->tpu->clk); + pm_runtime_put(&pwm->tpu->pdev->dev); + + pwm->timer_on = false; +} + +/* ----------------------------------------------------------------------------- + * PWM API + */ + +static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_device *tpu = to_tpu_device(chip); + struct tpu_pwm_device *pwm; + + if (_pwm->hwpwm >= TPU_CHANNEL_MAX) + return -EINVAL; + + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + if (pwm == NULL) + return -ENOMEM; + + pwm->tpu = tpu; + pwm->channel = _pwm->hwpwm; + pwm->polarity = tpu->pdata ? tpu->pdata->channels[pwm->channel].polarity + : PWM_POLARITY_NORMAL; + pwm->prescaler = 0; + pwm->period = 0; + pwm->duty = 0; + + pwm->timer_on = false; + + pwm_set_chip_data(_pwm, pwm); + + return 0; +} + +static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + + tpu_pwm_timer_stop(pwm); + kfree(pwm); +} + +static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm, + int duty_ns, int period_ns) +{ + static const unsigned int prescalers[] = { 1, 4, 16, 64 }; + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + struct tpu_device *tpu = to_tpu_device(chip); + unsigned int prescaler; + bool duty_only = false; + u32 clk_rate; + u32 period; + u32 duty; + int ret; + + /* + * Pick a prescaler to avoid overflowing the counter. + * TODO: Pick the highest acceptable prescaler. + */ + clk_rate = clk_get_rate(tpu->clk); + + for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) { + period = clk_rate / prescalers[prescaler] + / (NSEC_PER_SEC / period_ns); + if (period <= 0xffff) + break; + } + + if (prescaler == ARRAY_SIZE(prescalers) || period == 0) { + dev_err(&tpu->pdev->dev, "clock rate mismatch\n"); + return -ENOTSUPP; + } + + if (duty_ns) { + duty = clk_rate / prescalers[prescaler] + / (NSEC_PER_SEC / duty_ns); + if (duty > period) + return -EINVAL; + } else { + duty = 0; + } + + dev_dbg(&tpu->pdev->dev, + "rate %u, prescaler %u, period %u, duty %u\n", + clk_rate, prescalers[prescaler], period, duty); + + if (pwm->prescaler == prescaler && pwm->period == period) + duty_only = true; + + pwm->prescaler = prescaler; + pwm->period = period; + pwm->duty = duty; + + /* If the channel is disabled we're done. */ + if (!test_bit(PWMF_ENABLED, &_pwm->flags)) + return 0; + + if (duty_only && pwm->timer_on) { + /* + * If only the duty cycle changed and the timer is already + * running, there's no need to reconfigure it completely, Just + * modify the duty cycle. + */ + tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty); + dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", pwm->channel, + pwm->duty); + } else { + /* Otherwise perform a full reconfiguration. */ + ret = tpu_pwm_timer_start(pwm); + if (ret < 0) + return ret; + } + + if (duty == 0 || duty == period) { + /* + * To avoid running the timer when not strictly required, handle + * 0% and 100% duty cycles as fixed levels and stop the timer. + */ + tpu_pwm_set_pin(pwm, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE); + tpu_pwm_timer_stop(pwm); + } + + return 0; +} + +static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *_pwm, + enum pwm_polarity polarity) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + + pwm->polarity = polarity; + + return 0; +} + +static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + int ret; + + ret = tpu_pwm_timer_start(pwm); + if (ret < 0) + return ret; + + /* + * To avoid running the timer when not strictly required, handle 0% and + * 100% duty cycles as fixed levels and stop the timer. + */ + if (pwm->duty == 0 || pwm->duty == pwm->period) { + tpu_pwm_set_pin(pwm, pwm->duty ? + TPU_PIN_ACTIVE : TPU_PIN_INACTIVE); + tpu_pwm_timer_stop(pwm); + } + + return 0; +} + +static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *_pwm) +{ + struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + + /* The timer must be running to modify the pin output configuration. */ + tpu_pwm_timer_start(pwm); + tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE); + tpu_pwm_timer_stop(pwm); +} + +static const struct pwm_ops tpu_pwm_ops = { + .request = tpu_pwm_request, + .free = tpu_pwm_free, + .config = tpu_pwm_config, + .set_polarity = tpu_pwm_set_polarity, + .enable = tpu_pwm_enable, + .disable = tpu_pwm_disable, + .owner = THIS_MODULE, +}; + +/* ----------------------------------------------------------------------------- + * Probe and remove + */ + +static int tpu_probe(struct platform_device *pdev) +{ + struct tpu_device *tpu; + struct resource *res; + int ret; + + tpu = devm_kzalloc(&pdev->dev, sizeof(*tpu), GFP_KERNEL); + if (tpu == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + tpu->pdata = pdev->dev.platform_data; + + /* Map memory, get clock and pin control. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get I/O memory\n"); + return -ENXIO; + } + + tpu->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tpu->base)) + return PTR_ERR(tpu->base); + + tpu->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(tpu->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + return PTR_ERR(tpu->clk); + } + + /* Initialize and register the device. */ + platform_set_drvdata(pdev, tpu); + + spin_lock_init(&tpu->lock); + tpu->pdev = pdev; + + tpu->chip.dev = &pdev->dev; + tpu->chip.ops = &tpu_pwm_ops; + tpu->chip.base = -1; + tpu->chip.npwm = TPU_CHANNEL_MAX; + + ret = pwmchip_add(&tpu->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register PWM chip\n"); + return ret; + } + + dev_info(&pdev->dev, "TPU PWM %d registered\n", tpu->pdev->id); + + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int tpu_remove(struct platform_device *pdev) +{ + struct tpu_device *tpu = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&tpu->chip); + if (ret) + return ret; + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static struct platform_driver tpu_driver = { + .probe = tpu_probe, + .remove = tpu_remove, + .driver = { + .name = "renesas-tpu-pwm", + .owner = THIS_MODULE, + } +}; + +module_platform_driver(tpu_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Renesas TPU PWM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:renesas-tpu-pwm"); diff --git a/drivers/pwm/pwm-spear.c b/drivers/pwm/pwm-spear.c index 6d99e2cbdc73..a54d21401431 100644 --- a/drivers/pwm/pwm-spear.c +++ b/drivers/pwm/pwm-spear.c @@ -259,6 +259,7 @@ MODULE_DEVICE_TABLE(of, spear_pwm_of_match); static struct platform_driver spear_pwm_driver = { .driver = { .name = "spear-pwm", + .owner = THIS_MODULE, .of_match_table = spear_pwm_of_match, }, .probe = spear_pwm_probe, diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index a5402933001f..74298c561c4e 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -239,6 +239,7 @@ MODULE_DEVICE_TABLE(of, tegra_pwm_of_match); static struct platform_driver tegra_pwm_driver = { .driver = { .name = "tegra-pwm", + .owner = THIS_MODULE, .of_match_table = tegra_pwm_of_match, }, .probe = tegra_pwm_probe, diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index 48a485c2e422..aa4c5586f53b 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -359,7 +359,7 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) configure_polarity(pc, pwm->hwpwm); /* Enable TBCLK before enabling PWM device */ - ret = clk_prepare_enable(pc->tbclk); + ret = clk_enable(pc->tbclk); if (ret) { pr_err("Failed to enable TBCLK for %s\n", dev_name(pc->chip.dev)); @@ -395,7 +395,7 @@ static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); /* Disabling TBCLK on PWM disable */ - clk_disable_unprepare(pc->tbclk); + clk_disable(pc->tbclk); /* Stop Time base counter */ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT); @@ -482,6 +482,12 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev) return PTR_ERR(pc->tbclk); } + ret = clk_prepare(pc->tbclk); + if (ret < 0) { + dev_err(&pdev->dev, "clk_prepare() failed: %d\n", ret); + return ret; + } + ret = pwmchip_add(&pc->chip); if (ret < 0) { dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); @@ -508,6 +514,7 @@ pwmss_clk_failure: pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); pwmchip_remove(&pc->chip); + clk_unprepare(pc->tbclk); return ret; } @@ -515,6 +522,8 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev) { struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev); + clk_unprepare(pc->tbclk); + pm_runtime_get_sync(&pdev->dev); /* * Due to hardware misbehaviour, acknowledge of the stop_req diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c new file mode 100644 index 000000000000..8ca5de316d3b --- /dev/null +++ b/drivers/pwm/sysfs.c @@ -0,0 +1,352 @@ +/* + * A simple sysfs interface for the generic PWM framework + * + * Copyright (C) 2013 H Hartley Sweeten <hsweeten@visionengravers.com> + * + * Based on previous work by Lars Poeschel <poeschel@lemonage.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/kdev_t.h> +#include <linux/pwm.h> + +struct pwm_export { + struct device child; + struct pwm_device *pwm; +}; + +static struct pwm_export *child_to_pwm_export(struct device *child) +{ + return container_of(child, struct pwm_export, child); +} + +static struct pwm_device *child_to_pwm_device(struct device *child) +{ + struct pwm_export *export = child_to_pwm_export(child); + + return export->pwm; +} + +static ssize_t pwm_period_show(struct device *child, + struct device_attribute *attr, + char *buf) +{ + const struct pwm_device *pwm = child_to_pwm_device(child); + + return sprintf(buf, "%u\n", pwm->period); +} + +static ssize_t pwm_period_store(struct device *child, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pwm_device *pwm = child_to_pwm_device(child); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + ret = pwm_config(pwm, pwm->duty_cycle, val); + + return ret ? : size; +} + +static ssize_t pwm_duty_cycle_show(struct device *child, + struct device_attribute *attr, + char *buf) +{ + const struct pwm_device *pwm = child_to_pwm_device(child); + + return sprintf(buf, "%u\n", pwm->duty_cycle); +} + +static ssize_t pwm_duty_cycle_store(struct device *child, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pwm_device *pwm = child_to_pwm_device(child); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + ret = pwm_config(pwm, val, pwm->period); + + return ret ? : size; +} + +static ssize_t pwm_enable_show(struct device *child, + struct device_attribute *attr, + char *buf) +{ + const struct pwm_device *pwm = child_to_pwm_device(child); + int enabled = test_bit(PWMF_ENABLED, &pwm->flags); + + return sprintf(buf, "%d\n", enabled); +} + +static ssize_t pwm_enable_store(struct device *child, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pwm_device *pwm = child_to_pwm_device(child); + int val, ret; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + switch (val) { + case 0: + pwm_disable(pwm); + break; + case 1: + ret = pwm_enable(pwm); + break; + default: + ret = -EINVAL; + break; + } + + return ret ? : size; +} + +static ssize_t pwm_polarity_show(struct device *child, + struct device_attribute *attr, + char *buf) +{ + const struct pwm_device *pwm = child_to_pwm_device(child); + + return sprintf(buf, "%s\n", pwm->polarity ? "inversed" : "normal"); +} + +static ssize_t pwm_polarity_store(struct device *child, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct pwm_device *pwm = child_to_pwm_device(child); + enum pwm_polarity polarity; + int ret; + + if (sysfs_streq(buf, "normal")) + polarity = PWM_POLARITY_NORMAL; + else if (sysfs_streq(buf, "inversed")) + polarity = PWM_POLARITY_INVERSED; + else + return -EINVAL; + + ret = pwm_set_polarity(pwm, polarity); + + return ret ? : size; +} + +static DEVICE_ATTR(period, 0644, pwm_period_show, pwm_period_store); +static DEVICE_ATTR(duty_cycle, 0644, pwm_duty_cycle_show, pwm_duty_cycle_store); +static DEVICE_ATTR(enable, 0644, pwm_enable_show, pwm_enable_store); +static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store); + +static struct attribute *pwm_attrs[] = { + &dev_attr_period.attr, + &dev_attr_duty_cycle.attr, + &dev_attr_enable.attr, + &dev_attr_polarity.attr, + NULL +}; + +static const struct attribute_group pwm_attr_group = { + .attrs = pwm_attrs, +}; + +static const struct attribute_group *pwm_attr_groups[] = { + &pwm_attr_group, + NULL, +}; + +static void pwm_export_release(struct device *child) +{ + struct pwm_export *export = child_to_pwm_export(child); + + kfree(export); +} + +static int pwm_export_child(struct device *parent, struct pwm_device *pwm) +{ + struct pwm_export *export; + int ret; + + if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags)) + return -EBUSY; + + export = kzalloc(sizeof(*export), GFP_KERNEL); + if (!export) { + clear_bit(PWMF_EXPORTED, &pwm->flags); + return -ENOMEM; + } + + export->pwm = pwm; + + export->child.release = pwm_export_release; + export->child.parent = parent; + export->child.devt = MKDEV(0, 0); + export->child.groups = pwm_attr_groups; + dev_set_name(&export->child, "pwm%u", pwm->hwpwm); + + ret = device_register(&export->child); + if (ret) { + clear_bit(PWMF_EXPORTED, &pwm->flags); + kfree(export); + return ret; + } + + return 0; +} + +static int pwm_unexport_match(struct device *child, void *data) +{ + return child_to_pwm_device(child) == data; +} + +static int pwm_unexport_child(struct device *parent, struct pwm_device *pwm) +{ + struct device *child; + + if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags)) + return -ENODEV; + + child = device_find_child(parent, pwm, pwm_unexport_match); + if (!child) + return -ENODEV; + + /* for device_find_child() */ + put_device(child); + device_unregister(child); + pwm_put(pwm); + + return 0; +} + +static ssize_t pwm_export_store(struct device *parent, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct pwm_chip *chip = dev_get_drvdata(parent); + struct pwm_device *pwm; + unsigned int hwpwm; + int ret; + + ret = kstrtouint(buf, 0, &hwpwm); + if (ret < 0) + return ret; + + if (hwpwm >= chip->npwm) + return -ENODEV; + + pwm = pwm_request_from_chip(chip, hwpwm, "sysfs"); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + ret = pwm_export_child(parent, pwm); + if (ret < 0) + pwm_put(pwm); + + return ret ? : len; +} + +static ssize_t pwm_unexport_store(struct device *parent, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct pwm_chip *chip = dev_get_drvdata(parent); + unsigned int hwpwm; + int ret; + + ret = kstrtouint(buf, 0, &hwpwm); + if (ret < 0) + return ret; + + if (hwpwm >= chip->npwm) + return -ENODEV; + + ret = pwm_unexport_child(parent, &chip->pwms[hwpwm]); + + return ret ? : len; +} + +static ssize_t pwm_npwm_show(struct device *parent, + struct device_attribute *attr, + char *buf) +{ + const struct pwm_chip *chip = dev_get_drvdata(parent); + + return sprintf(buf, "%u\n", chip->npwm); +} + +static struct device_attribute pwm_chip_attrs[] = { + __ATTR(export, 0200, NULL, pwm_export_store), + __ATTR(unexport, 0200, NULL, pwm_unexport_store), + __ATTR(npwm, 0444, pwm_npwm_show, NULL), + __ATTR_NULL, +}; + +static struct class pwm_class = { + .name = "pwm", + .owner = THIS_MODULE, + .dev_attrs = pwm_chip_attrs, +}; + +static int pwmchip_sysfs_match(struct device *parent, const void *data) +{ + return dev_get_drvdata(parent) == data; +} + +void pwmchip_sysfs_export(struct pwm_chip *chip) +{ + struct device *parent; + + /* + * If device_create() fails the pwm_chip is still usable by + * the kernel its just not exported. + */ + parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip, + "pwmchip%d", chip->base); + if (IS_ERR(parent)) { + dev_warn(chip->dev, + "device_create failed for pwm_chip sysfs export\n"); + } +} + +void pwmchip_sysfs_unexport(struct pwm_chip *chip) +{ + struct device *parent; + + parent = class_find_device(&pwm_class, NULL, chip, + pwmchip_sysfs_match); + if (parent) { + /* for class_find_device() */ + put_device(parent); + device_unregister(parent); + } +} + +static int __init pwm_sysfs_init(void) +{ + return class_register(&pwm_class); +} +subsys_initcall(pwm_sysfs_init); |