[linux-yocto] [PATCH 3/7] pca9685: PCA9685 PWM and GPIO multi-function device.
Saul Wold
sgw at linux.intel.com
Tue Feb 16 14:01:32 PST 2016
From: Josef Ahmad <josef.ahmad at 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.puustinen at 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.ziemba at 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
More information about the linux-yocto
mailing list