From: Josef Ahmad <josef.ah...@linux.intel.com> There is also a driver for the same chip in drivers/pwm. This version has support for setting the output in GPIO mode in addition to the PWM mode.
Upstream-status: Forward-ported from Intel IOT Develper Kit Quark BSP Signed-off-by: Ismo Puustinen <ismo.puusti...@intel.com> --- drivers/mfd/Kconfig | 10 ++ drivers/mfd/Makefile | 2 + drivers/mfd/pca9685-core.c | 308 ++++++++++++++++++++++++++++++++++ drivers/mfd/pca9685-gpio.c | 108 ++++++++++++ drivers/mfd/pca9685-pwm.c | 262 +++++++++++++++++++++++++++++ drivers/mfd/pca9685.h | 110 ++++++++++++ include/linux/platform_data/pca9685.h | 51 ++++++ 7 files changed, 851 insertions(+) create mode 100644 drivers/mfd/pca9685-core.c create mode 100644 drivers/mfd/pca9685-gpio.c create mode 100644 drivers/mfd/pca9685-pwm.c create mode 100644 drivers/mfd/pca9685.h create mode 100644 include/linux/platform_data/pca9685.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d5ad04d..a7983b2 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -334,6 +334,16 @@ config MFD_INTEL_MSIC Passage) chip. This chip embeds audio, battery, GPIO, etc. devices used in Intel Medfield platforms. +config MFD_PCA9685 + tristate "NPX Semiconductors PCA9685 (PWM/GPIO) driver" + depends on GPIOLIB && I2C && PWM + select REGMAP_I2C + help + NPX PCA9685 I2C-bus PWM controller with GPIO output interface support. + The I2C-bus LED controller provides 16-channel, 12-bit PWM Fm+. + Additionally, the driver allows the channels to be configured as GPIO + interface (output only). + config MFD_IPAQ_MICRO bool "Atmel Micro ASIC (iPAQ h3100/h3600/h3700) Support" depends on SA1100_H3100 || SA1100_H3600 diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 0e5cfeb..d043a1b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -138,6 +138,8 @@ obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o +pca9685-objs := pca9685-core.o pca9685-gpio.o pca9685-pwm.o +obj-$(CONFIG_MFD_PCA9685) += pca9685.o obj-$(CONFIG_MFD_KEMPLD) += kempld-core.o obj-$(CONFIG_MFD_INTEL_QUARK_I2C_GPIO) += intel_quark_i2c_gpio.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/pca9685-core.c b/drivers/mfd/pca9685-core.c new file mode 100644 index 0000000..3f63b6d --- /dev/null +++ b/drivers/mfd/pca9685-core.c @@ -0,0 +1,308 @@ +/* + * Driver for NPX PCA9685 I2C-bus PWM controller with GPIO output interface + * support. + * + * Copyright(c) 2013-2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * The I2C-bus LED controller provides 16-channel, 12-bit PWM Fm+. + * Additionally, the driver allows the channels to be configured as GPIO + * interface (output only). + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pwm.h> +#include <linux/gpio.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/acpi.h> +#include <linux/property.h> + +#include "pca9685.h" + +static unsigned int en_invrt; +module_param(en_invrt, uint, 0); +MODULE_PARM_DESC(en_invrt, "Enable output logic state inverted mode"); + +static unsigned int en_open_dr; +module_param(en_open_dr, uint, 0); +MODULE_PARM_DESC(en_open_dr, + "The outputs are configured with an open-drain structure"); + +static int gpio_base = -1; /* requests dynamic ID allocation */ +module_param(gpio_base, int, 0); +MODULE_PARM_DESC(gpio_base, "GPIO base number"); + +static unsigned int pwm_period = PWM_PERIOD_DEF; /* PWM clock period */ +module_param(pwm_period, uint, 0); +MODULE_PARM_DESC(pwm_period, "PWM clock period (nanoseconds)"); + +static bool pca9685_register_volatile(struct device *dev, unsigned int reg) +{ + if (unlikely(reg == PCA9685_MODE1)) + return true; + else + return false; +} + +static struct regmap_config pca9685_regmap_i2c_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PCA9685_NUMREGS, + .volatile_reg = pca9685_register_volatile, + .cache_type = REGCACHE_RBTREE, +}; + +ssize_t pca9685_pwm_period_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pca9685 *pca = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", pca->pwm_period); +} + +ssize_t pca9685_pwm_period_sysfs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pca9685 *pca = dev_get_drvdata(dev); + unsigned period_ns; + int ret; + + sscanf(buf, "%u", &period_ns); + + ret = pca9685_update_prescale(pca, period_ns, true); + if (ret) + return ret; + + return count; +} + +/* Sysfs attribute to allow PWM clock period adjustment at run-time + * NOTE: All active channels will switch off momentarily if the + * PWM clock period is changed + */ +static DEVICE_ATTR(pwm_period, S_IWUSR | S_IRUGO, + pca9685_pwm_period_sysfs_show, + pca9685_pwm_period_sysfs_store); + +u8 default_chan_mapping[] = { + PWM_CH_GPIO, PWM_CH_PWM, + PWM_CH_GPIO, PWM_CH_PWM, + PWM_CH_GPIO, PWM_CH_PWM, + PWM_CH_GPIO, PWM_CH_PWM, + PWM_CH_GPIO, PWM_CH_PWM, + PWM_CH_GPIO, PWM_CH_PWM, + PWM_CH_GPIO, PWM_CH_GPIO, + PWM_CH_GPIO, PWM_CH_GPIO, + PWM_CH_DISABLED /* ALL_LED disabled */ +}; + +static int pca9685_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca9685_pdata *pdata; + struct pca9685 *pca; + int ret; + int mode2; + + pca = devm_kzalloc(&client->dev, sizeof(*pca), GFP_KERNEL); + if (unlikely(!pca)) + return -ENOMEM; + + pdata = client->dev.platform_data; + if (likely(pdata)) { + memcpy(pca->chan_mapping, pdata->chan_mapping, + ARRAY_SIZE(pca->chan_mapping)); + pca->gpio_base = pdata->gpio_base; + en_invrt = pdata->en_invrt; + en_open_dr = pdata->en_open_dr; + } else { + dev_warn(&client->dev, + "Platform data not provided." + "Using default or mod params configuration.\n"); +#if 1 + /* hack for Galileo 2*/ + pca->gpio_base = 64; + memcpy(pca->chan_mapping, default_chan_mapping, + ARRAY_SIZE(pca->chan_mapping)); +#else + pca->gpio_base = gpio_base; + memset(pca->chan_mapping, PWM_CH_UNDEFINED, + ARRAY_SIZE(pca->chan_mapping)); +#endif + } + + if (unlikely(!i2c_check_functionality(client->adapter, + I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE_DATA))) { + dev_err(&client->dev, + "i2c adapter doesn't support required functionality\n"); + return -EIO; + } + + 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); + + /* registration of GPIO chip */ + pca->gpio_chip.label = "pca9685-gpio"; + pca->gpio_chip.owner = THIS_MODULE; + pca->gpio_chip.set = pca9685_gpio_set; + pca->gpio_chip.get = pca9685_gpio_get; + pca->gpio_chip.can_sleep = 1; + pca->gpio_chip.ngpio = PCA9685_MAXCHAN; + pca->gpio_chip.base = pca->gpio_base; + pca->gpio_chip.request = pca9685_gpio_request; + pca->gpio_chip.free = pca9685_gpio_free; + + mutex_init(&pca->lock); + + ret = gpiochip_add(&pca->gpio_chip); + if (unlikely(ret < 0)) { + dev_err(&client->dev, "Could not register gpiochip, %d\n", ret); + goto err; + } + + /* configure initial PWM settings */ + ret = pca9685_init_pwm_regs(pca, pwm_period); + if (ret) { + pr_err("Failed to initialize PWM registers\n"); + goto err_gpiochip; + } + + /* registration of PWM chip */ + + regmap_read(pca->regmap, PCA9685_MODE2, &mode2); + + /* update mode2 register */ + if (en_invrt) + mode2 |= MODE2_INVRT; + else + mode2 &= ~MODE2_INVRT; + + if (en_open_dr) + mode2 &= ~MODE2_OUTDRV; + else + mode2 |= MODE2_OUTDRV; + + regmap_write(pca->regmap, PCA9685_MODE2, mode2); + + pca->pwm_chip.ops = &pca9685_pwm_ops; + /* add an extra channel for ALL_LED */ + pca->pwm_chip.npwm = PCA9685_MAXCHAN + 1; + pca->pwm_chip.dev = &client->dev; + pca->pwm_chip.base = -1; + + ret = pwmchip_add(&pca->pwm_chip); + if (unlikely(ret < 0)) { + dev_err(&client->dev, "pwmchip_add failed %d\n", ret); + goto err_gpiochip; + } + + /* Also create a sysfs interface, providing a cmd line config option */ + ret = sysfs_create_file(&client->dev.kobj, &dev_attr_pwm_period.attr); + if (unlikely(ret < 0)) { + dev_err(&client->dev, "sysfs_create_file failed %d\n", ret); + goto err_pwmchip; + } + + return ret; + +err_pwmchip: + if (unlikely(pwmchip_remove(&pca->pwm_chip))) + dev_warn(&client->dev, "%s failed\n", "pwmchip_remove()"); + +err_gpiochip: + gpiochip_remove(&pca->gpio_chip); +err: + mutex_destroy(&pca->lock); + + return ret; +} + +static int pca9685_remove(struct i2c_client *client) +{ + struct pca9685 *pca = i2c_get_clientdata(client); + int ret; + + regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP, + MODE1_SLEEP); + + gpiochip_remove(&pca->gpio_chip); + + sysfs_remove_file(&client->dev.kobj, &dev_attr_pwm_period.attr); + + ret = pwmchip_remove(&pca->pwm_chip); + if (unlikely(ret)) + dev_err(&client->dev, "%s failed, %d\n", + "pwmchip_remove()", ret); + + mutex_destroy(&pca->lock); + + return ret; +} + +static const struct acpi_device_id pca9685_acpi_ids[] = { + { "INT3492", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, pca9685_acpi_ids); + +static const struct i2c_device_id pca9685_id[] = { + { "pca9685", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, pca9685_id); + +static struct i2c_driver pca9685_i2c_driver = { + .driver = { + .name = "mfd-pca9685", + .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(pca9685_acpi_ids), + }, + .probe = pca9685_probe, + .remove = pca9685_remove, + .id_table = pca9685_id, +}; + +static int __init pca9685_init(void) +{ + if (unlikely((pwm_period < PWM_PERIOD_MIN) || + (PWM_PERIOD_MAX < pwm_period))) { + pr_err("Invalid PWM period specified (valid range: %d-%d)\n", + PWM_PERIOD_MIN, PWM_PERIOD_MAX); + return -EINVAL; + } + + return i2c_add_driver(&pca9685_i2c_driver); +} +/* register after i2c postcore initcall */ +subsys_initcall(pca9685_init); + +static void __exit pca9685_exit(void) +{ + i2c_del_driver(&pca9685_i2c_driver); +} +module_exit(pca9685_exit); + +MODULE_AUTHOR("Wojciech Ziemba <wojciech.zie...@emutex.com>"); +MODULE_DESCRIPTION("NPX Semiconductors PCA9685 (PWM/GPIO) driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/pca9685-gpio.c b/drivers/mfd/pca9685-gpio.c new file mode 100644 index 0000000..ed348af --- /dev/null +++ b/drivers/mfd/pca9685-gpio.c @@ -0,0 +1,108 @@ +/* + * Driver for NPX PCA9685 I2C-bus PWM controller with GPIO output interface + * support. + * + * Copyright(c) 2013-2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * The I2C-bus LED controller provides 16-channel, 12-bit PWM Fm+. + * Additionally, the driver allows the channels to be configured as GPIO + * interface (output only). + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/pwm.h> +#include <linux/regmap.h> + +#include "pca9685.h" + +static inline struct pca9685 *gpio_to_pca(struct gpio_chip *gpio_chip) +{ + return container_of(gpio_chip, struct pca9685, gpio_chip); +} + +static inline int is_gpio_allowed(const struct pca9685 *pca, unsigned channel) +{ + return pca->chan_mapping[channel] & PWM_CH_GPIO; +} + +int pca9685_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct pca9685 *pca; + struct pwm_device *pwm; + int ret = 0; + pca = gpio_to_pca(chip); + + /* validate channel constrains */ + if (!is_gpio_allowed(pca, offset)) + return -ENODEV; + + /* return busy if channel is already allocated for pwm */ + pwm = &pca->pwm_chip.pwms[offset]; + if (test_bit(PWMF_REQUESTED, &pwm->flags)) + return -EBUSY; + + /* clear the on counter */ + regmap_write(pca->regmap, LED_N_ON_L(offset), 0x0); + regmap_write(pca->regmap, LED_N_ON_H(offset), 0x0); + + /* clear the off counter */ + regmap_write(pca->regmap, LED_N_OFF_L(offset), 0x0); + ret = regmap_write(pca->regmap, LED_N_OFF_H(offset), 0x0); + + clear_sleep_bit(pca); + + return ret; +} + +void pca9685_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + struct pca9685 *pca; + + pca = gpio_to_pca(chip); + + /* clear the on counter reg */ + regmap_write(pca->regmap, LED_N_ON_L(offset), 0x0); + regmap_write(pca->regmap, LED_N_ON_H(offset), 0x0); + + set_sleep_bit(pca); + + return; +} + +void pca9685_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct pca9685 *pca; + + pca = gpio_to_pca(chip); + + /* set the full-on bit */ + regmap_write(pca->regmap, LED_N_ON_H(offset), (value << 4) & LED_FULL); + + return; +} + +int pca9685_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct pca9685 *pca; + unsigned int val; + + pca = gpio_to_pca(chip); + + /* read the full-on bit */ + regmap_read(pca->regmap, LED_N_ON_H(offset), &val); + + return !!val; +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/pca9685-pwm.c b/drivers/mfd/pca9685-pwm.c new file mode 100644 index 0000000..0c05263 --- /dev/null +++ b/drivers/mfd/pca9685-pwm.c @@ -0,0 +1,262 @@ +/* + * Driver for NPX PCA9685 I2C-bus PWM controller with GPIO output interface + * support. + * + * Copyright(c) 2013-2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * The I2C-bus LED controller provides 16-channel, 12-bit PWM Fm+. + * Additionally, the driver allows the channels to be configured as GPIO + * interface (output only). + */ + +#include <linux/module.h> +#include <linux/pwm.h> +#include <linux/gpio.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/delay.h> + +#include "pca9685.h" + +static inline struct pca9685 *pwm_to_pca(struct pwm_chip *pwm_chip) +{ + return container_of(pwm_chip, struct pca9685, pwm_chip); +} + +static inline int period_ns_to_prescale(unsigned period_ns) +{ + return (DIV_ROUND_CLOSEST(OSC_CLK_MHZ * period_ns, + SAMPLE_RES * 1000)) - 1; +} + +static inline int is_pwm_allowed(const struct pca9685 *pca, unsigned channel) +{ + return pca->chan_mapping[channel] & PWM_CH_PWM; +} + +int pca9685_update_prescale(struct pca9685 *pca, unsigned period_ns, + bool reconfigure_channels) +{ + int pre_scale, i; + struct pwm_device *pwm; + unsigned long long duty_scale; + unsigned long long new_duty_ns; + + if (unlikely((period_ns < PWM_PERIOD_MIN) || + (PWM_PERIOD_MAX < period_ns))) { + pr_err("Invalid PWM period specified (valid range: %d-%d)\n", + PWM_PERIOD_MIN, PWM_PERIOD_MAX); + return -EINVAL; + } + + mutex_lock(&pca->lock); + + /* update pre_scale to the closest period */ + pre_scale = period_ns_to_prescale(period_ns); + /* ensure sleep-mode bit is set + * NOTE: All active channels will switch off for at least 500 usecs + */ + regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_SLEEP, MODE1_SLEEP); + regmap_write(pca->regmap, PCA9685_PRESCALE, pre_scale); + /* clear sleep mode flag if at least 1 channel is active */ + if (pca->active_cnt > 0) { + regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_SLEEP, 0x0); + usleep_range(MODE1_RESTART_DELAY, MODE1_RESTART_DELAY * 2); + regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_RESTART, MODE1_RESTART); + } + + if (reconfigure_channels) { + for (i = 0; i < pca->pwm_chip.npwm; i++) { + pwm = &pca->pwm_chip.pwms[i]; + pwm->period = period_ns; + if (pwm->duty_cycle > 0) { + /* Scale the rise time to maintain duty cycle */ + duty_scale = period_ns; + duty_scale *= 1000000; + do_div(duty_scale, pca->pwm_period); + new_duty_ns = duty_scale * pwm->duty_cycle; + do_div(new_duty_ns, 1000000); + /* Update the duty_cycle */ + pwm_config(pwm, (int)new_duty_ns, pwm->period); + } + } + } + pca->pwm_period = period_ns; + + mutex_unlock(&pca->lock); + return 0; +} + +int pca9685_init_pwm_regs(struct pca9685 *pca, unsigned period_ns) +{ + int ret, chan; + + /* set MODE1_SLEEP */ + ret = regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_SLEEP, MODE1_SLEEP); + if (unlikely(ret < 0)) + return ret; + + /* configure the initial PWM clock period */ + ret = pca9685_update_prescale(pca, period_ns, false); + if (unlikely(ret < 0)) + return ret; + + /* reset PWM channel registers to power-on default values */ + for (chan = 0; chan < PCA9685_MAXCHAN; chan++) { + ret = regmap_write(pca->regmap, LED_N_ON_L(chan), 0); + if (unlikely(ret < 0)) + return ret; + ret = regmap_write(pca->regmap, LED_N_ON_H(chan), 0); + if (unlikely(ret < 0)) + return ret; + ret = regmap_write(pca->regmap, LED_N_OFF_L(chan), 0); + if (unlikely(ret < 0)) + return ret; + ret = regmap_write(pca->regmap, LED_N_OFF_H(chan), LED_FULL); + if (unlikely(ret < 0)) + return ret; + } + /* reset ALL_LED registers to power-on default values */ + ret = regmap_write(pca->regmap, PCA9685_ALL_LED_ON_L, 0); + if (unlikely(ret < 0)) + return ret; + ret = regmap_write(pca->regmap, PCA9685_ALL_LED_ON_H, 0); + if (unlikely(ret < 0)) + return ret; + ret = regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0); + if (unlikely(ret < 0)) + return ret; + ret = regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, LED_FULL); + if (unlikely(ret < 0)) + return ret; + + return ret; +} + +static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pca9685 *pca = pwm_to_pca(chip); + unsigned long long duty; + unsigned int reg_on_h, + reg_off_l, + reg_off_h; + int full_off; + + /* Changing PWM period for a single channel at run-time not allowed. + * The PCA9685 PWM clock is shared across all PWM channels + */ + if (unlikely(period_ns != pwm->period)) + return -EPERM; + + if (unlikely(pwm->hwpwm >= PCA9685_MAXCHAN)) { + reg_on_h = PCA9685_ALL_LED_ON_H; + reg_off_l = PCA9685_ALL_LED_OFF_L; + reg_off_h = PCA9685_ALL_LED_OFF_H; + } else { + reg_on_h = LED_N_ON_H(pwm->hwpwm); + reg_off_l = LED_N_OFF_L(pwm->hwpwm); + reg_off_h = LED_N_OFF_H(pwm->hwpwm); + } + + duty = SAMPLE_RES * (unsigned long long)duty_ns; + duty = DIV_ROUND_UP_ULL(duty, period_ns); + + if (duty >= SAMPLE_RES) /* set the LED_FULL bit */ + return regmap_write(pca->regmap, reg_on_h, LED_FULL); + else /* clear the LED_FULL bit */ + regmap_write(pca->regmap, reg_on_h, 0x00); + + full_off = !test_bit(PWMF_ENABLED, &pwm->flags) << 4; + + regmap_write(pca->regmap, reg_off_l, (int)duty & 0xff); + + return regmap_write(pca->regmap, reg_off_h, + ((int)duty >> 8 | full_off) & 0x1f); +} + +static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = pwm_to_pca(chip); + int ret; + + unsigned int reg_off_h; + + if (unlikely(pwm->hwpwm >= PCA9685_MAXCHAN)) + reg_off_h = PCA9685_ALL_LED_OFF_H; + else + reg_off_h = LED_N_OFF_H(pwm->hwpwm); + + /* clear the full-off bit */ + ret = regmap_update_bits(pca->regmap, reg_off_h, LED_FULL, 0x0); + + clear_sleep_bit(pca); + + return ret; +} + +static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca = pwm_to_pca(chip); + + unsigned int reg_off_h; + + if (unlikely(pwm->hwpwm >= PCA9685_MAXCHAN)) + reg_off_h = PCA9685_ALL_LED_OFF_H; + else + reg_off_h = LED_N_OFF_H(pwm->hwpwm); + + /* set the LED_OFF counter. */ + regmap_update_bits(pca->regmap, reg_off_h, LED_FULL, LED_FULL); + + set_sleep_bit(pca); + + return; +} + +static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pca9685 *pca; + struct gpio_chip *gpio_chip; + unsigned channel = pwm->hwpwm; + + pca = pwm_to_pca(chip); + + /* validate channel constrains */ + if (!is_pwm_allowed(pca, channel)) + return -ENODEV; + + /* return busy if channel is already allocated for gpio */ + gpio_chip = &pca->gpio_chip; + + if ((channel < PCA9685_MAXCHAN) && + (gpiochip_is_requested(gpio_chip, channel))) + return -EBUSY; + + pwm->period = pca->pwm_period; + + return 0; +} + +const struct pwm_ops pca9685_pwm_ops = { + .enable = pca9685_pwm_enable, + .disable = pca9685_pwm_disable, + .config = pca9685_pwm_config, + .request = pca9685_pwm_request, + .owner = THIS_MODULE, +}; + +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/pca9685.h b/drivers/mfd/pca9685.h new file mode 100644 index 0000000..d678d30 --- /dev/null +++ b/drivers/mfd/pca9685.h @@ -0,0 +1,110 @@ +/* + * Driver for NPX PCA9685 I2C-bus PWM controller with GPIO output interface + * support. + * + * Copyright(c) 2013-2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * The I2C-bus LED controller provides 16-channel, 12-bit PWM Fm+. + * Additionally, the driver allows the channels to be configured as GPIO + * interface (output only). + */ + +#ifndef __LINUX_MFD_PCA9685_H +#define __LINUX_MFD_PCA9685_H + +#include <linux/mutex.h> +#include <linux/gpio.h> +#include <linux/pwm.h> +#include <linux/platform_data/pca9685.h> + +#define PCA9685_MODE1 0x00 +#define PCA9685_MODE2 0x01 +#define PCA9685_SUBADDR1 0x02 +#define PCA9685_SUBADDR2 0x03 +#define PCA9685_SUBADDR3 0x04 +#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 LED_FULL (1 << 4) +#define MODE1_SLEEP (1 << 4) +#define MODE1_RESTART (1 << 7) + +#define MODE1_RESTART_DELAY 500 + +#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))) + +#define OSC_CLK_MHZ 25 /* 25 MHz */ +#define SAMPLE_RES 4096 /* 12 bits */ +#define PWM_PERIOD_MIN 666666 /* ~1525 Hz */ +#define PWM_PERIOD_MAX 41666666 /* 24 Hz */ +#define PWM_PERIOD_DEF 5000000 /* default 200 Hz */ + +struct pca9685 { + struct gpio_chip gpio_chip; + struct pwm_chip pwm_chip; + struct regmap *regmap; + struct mutex lock; /* mutual exclusion semaphore */ + /* Array of channel allocation constrains */ + /* add an extra channel for ALL_LED */ + u8 chan_mapping[PCA9685_MAXCHAN + 1]; + int gpio_base; + int active_cnt; + int pwm_exported_cnt; + int pwm_period; +}; + +extern const struct pwm_ops pca9685_pwm_ops; + +int pca9685_gpio_request(struct gpio_chip *chip, unsigned offset); +void pca9685_gpio_free(struct gpio_chip *chip, unsigned offset); +void pca9685_gpio_set(struct gpio_chip *chip, unsigned offset, int value); +int pca9685_gpio_get(struct gpio_chip *chip, unsigned offset); + +int pca9685_init_pwm_regs(struct pca9685 *pca, unsigned period_ns); +int pca9685_update_prescale(struct pca9685 *pca, unsigned period_ns, + bool reconfigure_channels); + +static inline void set_sleep_bit(struct pca9685 *pca) +{ + mutex_lock(&pca->lock); + /* set sleep mode flag if no more active LED channel*/ + if (--pca->active_cnt == 0) + regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP, + MODE1_SLEEP); + mutex_unlock(&pca->lock); +} + +static inline void clear_sleep_bit(struct pca9685 *pca) +{ + mutex_lock(&pca->lock); + /* clear sleep mode flag if at least 1 LED channel is active */ + if (pca->active_cnt++ == 0) + regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_SLEEP, 0x0); + + mutex_unlock(&pca->lock); +} + +#endif /* __LINUX_MFD_PCA9685_H */ diff --git a/include/linux/platform_data/pca9685.h b/include/linux/platform_data/pca9685.h new file mode 100644 index 0000000..dbb83f7 --- /dev/null +++ b/include/linux/platform_data/pca9685.h @@ -0,0 +1,51 @@ +/* + * Platform data for pca9685 driver + * + * Copyright(c) 2013-2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#ifndef _PLAT_PCA9685_H_ +#define _PLAT_PCA9685_H_ + +#define PCA9685_MAXCHAN 16 +#define MODE2_INVRT (1 << 4) +#define MODE2_OUTDRV (1 << 2) + +/* PWM channel allocation flags */ +enum { + PWM_CH_DISABLED = 0, + PWM_CH_PWM = 1 << 0, + PWM_CH_GPIO = 1 << 1, + /* allow PWM or GPIO */ + PWM_CH_UNDEFINED = PWM_CH_PWM | PWM_CH_GPIO, +}; + +/** + * struct pca9685_pdata - Platform data for pca9685 driver + * @chan_mapping: Array of channel allocation constrains + * @gpio_base: GPIO base + * mode2_flags: mode2 register modification flags: INVRT and OUTDRV + **/ +struct pca9685_pdata { + /* Array of channel allocation constrains */ + /* add an extra channel for ALL_LED */ + u8 chan_mapping[PCA9685_MAXCHAN + 1]; + /* GPIO base */ + int gpio_base; + /* mode2 flags */ + u8 en_invrt:1, /* enable output logic state inverted mode */ + en_open_dr:1, /* enable if outputs are configured with an + open-drain structure */ + unused:6; +}; + +#endif /* _PLAT_PCA9685_H_ */ -- 2.5.0 -- _______________________________________________ linux-yocto mailing list linux-yocto@yoctoproject.org https://lists.yoctoproject.org/listinfo/linux-yocto