diff options
Diffstat (limited to 'kernel/drivers/pwm/pwm-pca9685.c')
-rw-r--r-- | kernel/drivers/pwm/pwm-pca9685.c | 110 |
1 files changed, 104 insertions, 6 deletions
diff --git a/kernel/drivers/pwm/pwm-pca9685.c b/kernel/drivers/pwm/pwm-pca9685.c index 34b5c275a..117fccf79 100644 --- a/kernel/drivers/pwm/pwm-pca9685.c +++ b/kernel/drivers/pwm/pwm-pca9685.c @@ -2,6 +2,7 @@ * Driver for PCA9685 16-channel 12-bit PWM LED controller * * Copyright (C) 2013 Steffen Trumtrar <s.trumtrar@pengutronix.de> + * Copyright (C) 2015 Clemens Gruber <clemens.gruber@pqgruber.com> * * based on the pwm-twl-led.c driver * @@ -18,12 +19,23 @@ * this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <linux/acpi.h> #include <linux/i2c.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/pwm.h> #include <linux/regmap.h> #include <linux/slab.h> +#include <linux/delay.h> + +/* + * Because the PCA9685 has only one prescaler per chip, changing the period of + * one channel affects the period of all 16 PWM outputs! + * However, the ratio between each configured duty cycle and the chip-wide + * period remains constant, because the OFF time is set in proportion to the + * counter range. + */ #define PCA9685_MODE1 0x00 #define PCA9685_MODE2 0x01 @@ -42,10 +54,18 @@ #define PCA9685_ALL_LED_OFF_H 0xFD #define PCA9685_PRESCALE 0xFE +#define PCA9685_PRESCALE_MIN 0x03 /* => max. frequency of 1526 Hz */ +#define PCA9685_PRESCALE_MAX 0xFF /* => min. frequency of 24 Hz */ + +#define PCA9685_COUNTER_RANGE 4096 +#define PCA9685_DEFAULT_PERIOD 5000000 /* Default period_ns = 1/200 Hz */ +#define PCA9685_OSC_CLOCK_MHZ 25 /* Internal oscillator with 25 MHz */ + #define PCA9685_NUMREGS 0xFF #define PCA9685_MAXCHAN 0x10 #define LED_FULL (1 << 4) +#define MODE1_RESTART (1 << 7) #define MODE1_SLEEP (1 << 4) #define MODE2_INVRT (1 << 4) #define MODE2_OUTDRV (1 << 2) @@ -59,6 +79,8 @@ struct pca9685 { struct pwm_chip chip; struct regmap *regmap; int active_cnt; + int duty_ns; + int period_ns; }; static inline struct pca9685 *to_pca(struct pwm_chip *chip) @@ -72,6 +94,47 @@ static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, struct pca9685 *pca = to_pca(chip); unsigned long long duty; unsigned int reg; + int prescale; + + if (period_ns != pca->period_ns) { + prescale = DIV_ROUND_CLOSEST(PCA9685_OSC_CLOCK_MHZ * period_ns, + PCA9685_COUNTER_RANGE * 1000) - 1; + + if (prescale >= PCA9685_PRESCALE_MIN && + prescale <= PCA9685_PRESCALE_MAX) { + /* Put chip into sleep mode */ + regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_SLEEP, MODE1_SLEEP); + + /* Change the chip-wide output frequency */ + regmap_write(pca->regmap, PCA9685_PRESCALE, prescale); + + /* Wake the chip up */ + regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_SLEEP, 0x0); + + /* Wait 500us for the oscillator to be back up */ + udelay(500); + + pca->period_ns = period_ns; + + /* + * If the duty cycle did not change, restart PWM with + * the same duty cycle to period ratio and return. + */ + if (duty_ns == pca->duty_ns) { + regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_RESTART, 0x1); + return 0; + } + } else { + dev_err(chip->dev, + "prescaler not set: period out of bounds!\n"); + return -EINVAL; + } + } + + pca->duty_ns = duty_ns; if (duty_ns < 1) { if (pwm->hwpwm >= PCA9685_MAXCHAN) @@ -85,6 +148,22 @@ static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, } if (duty_ns == period_ns) { + /* Clear both OFF registers */ + 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); + + if (pwm->hwpwm >= PCA9685_MAXCHAN) + reg = PCA9685_ALL_LED_OFF_H; + else + reg = LED_N_OFF_H(pwm->hwpwm); + + regmap_write(pca->regmap, reg, 0x0); + + /* Set the full ON bit */ if (pwm->hwpwm >= PCA9685_MAXCHAN) reg = PCA9685_ALL_LED_ON_H; else @@ -95,7 +174,7 @@ static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } - duty = 4096 * (unsigned long long)duty_ns; + duty = PCA9685_COUNTER_RANGE * (unsigned long long)duty_ns; duty = DIV_ROUND_UP_ULL(duty, period_ns); if (pwm->hwpwm >= PCA9685_MAXCHAN) @@ -112,6 +191,14 @@ static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf); + /* Clear the full ON bit, otherwise the set OFF time has no effect */ + 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); + return 0; } @@ -212,7 +299,6 @@ static const struct regmap_config pca9685_regmap_i2c_config = { 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; @@ -228,17 +314,19 @@ static int pca9685_pwm_probe(struct i2c_client *client, ret); return ret; } + pca->duty_ns = 0; + pca->period_ns = PCA9685_DEFAULT_PERIOD; i2c_set_clientdata(client, pca); regmap_read(pca->regmap, PCA9685_MODE2, &mode2); - if (of_property_read_bool(np, "invert")) + if (device_property_read_bool(&client->dev, "invert")) mode2 |= MODE2_INVRT; else mode2 &= ~MODE2_INVRT; - if (of_property_read_bool(np, "open-drain")) + if (device_property_read_bool(&client->dev, "open-drain")) mode2 &= ~MODE2_OUTDRV; else mode2 |= MODE2_OUTDRV; @@ -276,17 +364,27 @@ static const struct i2c_device_id pca9685_id[] = { }; MODULE_DEVICE_TABLE(i2c, pca9685_id); +#ifdef CONFIG_ACPI +static const struct acpi_device_id pca9685_acpi_ids[] = { + { "INT3492", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, pca9685_acpi_ids); +#endif + +#ifdef CONFIG_OF static const struct of_device_id pca9685_dt_ids[] = { { .compatible = "nxp,pca9685-pwm", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, pca9685_dt_ids); +#endif static struct i2c_driver pca9685_i2c_driver = { .driver = { .name = "pca9685-pwm", - .owner = THIS_MODULE, - .of_match_table = pca9685_dt_ids, + .acpi_match_table = ACPI_PTR(pca9685_acpi_ids), + .of_match_table = of_match_ptr(pca9685_dt_ids), }, .probe = pca9685_pwm_probe, .remove = pca9685_pwm_remove, |