linux/drivers/pwm/pwm-twl.c
<<
>>
Prefs
   1/*
   2 * Driver for TWL4030/6030 Generic Pulse Width Modulator
   3 *
   4 * Copyright (C) 2012 Texas Instruments
   5 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify it
   8 * under the terms of the GNU General Public License version 2 as published by
   9 * the Free Software Foundation.
  10 *
  11 * This program is distributed in the hope that it will be useful, but WITHOUT
  12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  14 * more details.
  15 *
  16 * You should have received a copy of the GNU General Public License along with
  17 * this program.  If not, see <http://www.gnu.org/licenses/>.
  18 */
  19
  20#include <linux/module.h>
  21#include <linux/platform_device.h>
  22#include <linux/pwm.h>
  23#include <linux/i2c/twl.h>
  24#include <linux/slab.h>
  25
  26/*
  27 * This driver handles the PWMs of TWL4030 and TWL6030.
  28 * The TRM names for the PWMs on TWL4030 are: PWM0, PWM1
  29 * TWL6030 also have two PWMs named in the TRM as PWM1, PWM2
  30 */
  31
  32#define TWL_PWM_MAX             0x7f
  33
  34/* Registers, bits and macro for TWL4030 */
  35#define TWL4030_GPBR1_REG       0x0c
  36#define TWL4030_PMBR1_REG       0x0d
  37
  38/* GPBR1 register bits */
  39#define TWL4030_PWMXCLK_ENABLE  (1 << 0)
  40#define TWL4030_PWMX_ENABLE     (1 << 2)
  41#define TWL4030_PWMX_BITS       (TWL4030_PWMX_ENABLE | TWL4030_PWMXCLK_ENABLE)
  42#define TWL4030_PWM_TOGGLE(pwm, x)      ((x) << (pwm))
  43
  44/* PMBR1 register bits */
  45#define TWL4030_GPIO6_PWM0_MUTE_MASK            (0x03 << 2)
  46#define TWL4030_GPIO6_PWM0_MUTE_PWM0            (0x01 << 2)
  47#define TWL4030_GPIO7_VIBRASYNC_PWM1_MASK       (0x03 << 4)
  48#define TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1       (0x03 << 4)
  49
  50/* Register, bits and macro for TWL6030 */
  51#define TWL6030_TOGGLE3_REG     0x92
  52
  53#define TWL6030_PWMXR           (1 << 0)
  54#define TWL6030_PWMXS           (1 << 1)
  55#define TWL6030_PWMXEN          (1 << 2)
  56#define TWL6030_PWM_TOGGLE(pwm, x)      ((x) << (pwm * 3))
  57
  58struct twl_pwm_chip {
  59        struct pwm_chip chip;
  60        struct mutex mutex;
  61        u8 twl6030_toggle3;
  62        u8 twl4030_pwm_mux;
  63};
  64
  65static inline struct twl_pwm_chip *to_twl(struct pwm_chip *chip)
  66{
  67        return container_of(chip, struct twl_pwm_chip, chip);
  68}
  69
  70static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
  71                              int duty_ns, int period_ns)
  72{
  73        int duty_cycle = DIV_ROUND_UP(duty_ns * TWL_PWM_MAX, period_ns) + 1;
  74        u8 pwm_config[2] = { 1, 0 };
  75        int base, ret;
  76
  77        /*
  78         * To configure the duty period:
  79         * On-cycle is set to 1 (the minimum allowed value)
  80         * The off time of 0 is not configurable, so the mapping is:
  81         * 0 -> off cycle = 2,
  82         * 1 -> off cycle = 2,
  83         * 2 -> off cycle = 3,
  84         * 126 - > off cycle 127,
  85         * 127 - > off cycle 1
  86         * When on cycle == off cycle the PWM will be always on
  87         */
  88        if (duty_cycle == 1)
  89                duty_cycle = 2;
  90        else if (duty_cycle > TWL_PWM_MAX)
  91                duty_cycle = 1;
  92
  93        base = pwm->hwpwm * 3;
  94
  95        pwm_config[1] = duty_cycle;
  96
  97        ret = twl_i2c_write(TWL_MODULE_PWM, pwm_config, base, 2);
  98        if (ret < 0)
  99                dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
 100
 101        return ret;
 102}
 103
 104static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 105{
 106        struct twl_pwm_chip *twl = to_twl(chip);
 107        int ret;
 108        u8 val;
 109
 110        mutex_lock(&twl->mutex);
 111        ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
 112        if (ret < 0) {
 113                dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
 114                goto out;
 115        }
 116
 117        val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
 118
 119        ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
 120        if (ret < 0)
 121                dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
 122
 123        val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
 124
 125        ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
 126        if (ret < 0)
 127                dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
 128
 129out:
 130        mutex_unlock(&twl->mutex);
 131        return ret;
 132}
 133
 134static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 135{
 136        struct twl_pwm_chip *twl = to_twl(chip);
 137        int ret;
 138        u8 val;
 139
 140        mutex_lock(&twl->mutex);
 141        ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
 142        if (ret < 0) {
 143                dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
 144                goto out;
 145        }
 146
 147        val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
 148
 149        ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
 150        if (ret < 0)
 151                dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
 152
 153        val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
 154
 155        ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
 156        if (ret < 0)
 157                dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
 158
 159out:
 160        mutex_unlock(&twl->mutex);
 161}
 162
 163static int twl4030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
 164{
 165        struct twl_pwm_chip *twl = to_twl(chip);
 166        int ret;
 167        u8 val, mask, bits;
 168
 169        if (pwm->hwpwm == 1) {
 170                mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
 171                bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1;
 172        } else {
 173                mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
 174                bits = TWL4030_GPIO6_PWM0_MUTE_PWM0;
 175        }
 176
 177        mutex_lock(&twl->mutex);
 178        ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
 179        if (ret < 0) {
 180                dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
 181                goto out;
 182        }
 183
 184        /* Save the current MUX configuration for the PWM */
 185        twl->twl4030_pwm_mux &= ~mask;
 186        twl->twl4030_pwm_mux |= (val & mask);
 187
 188        /* Select PWM functionality */
 189        val &= ~mask;
 190        val |= bits;
 191
 192        ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
 193        if (ret < 0)
 194                dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label);
 195
 196out:
 197        mutex_unlock(&twl->mutex);
 198        return ret;
 199}
 200
 201static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
 202{
 203        struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
 204                                                chip);
 205        int ret;
 206        u8 val, mask;
 207
 208        if (pwm->hwpwm == 1)
 209                mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
 210        else
 211                mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
 212
 213        mutex_lock(&twl->mutex);
 214        ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
 215        if (ret < 0) {
 216                dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
 217                goto out;
 218        }
 219
 220        /* Restore the MUX configuration for the PWM */
 221        val &= ~mask;
 222        val |= (twl->twl4030_pwm_mux & mask);
 223
 224        ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
 225        if (ret < 0)
 226                dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label);
 227
 228out:
 229        mutex_unlock(&twl->mutex);
 230}
 231
 232static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 233{
 234        struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
 235                                                chip);
 236        int ret;
 237        u8 val;
 238
 239        mutex_lock(&twl->mutex);
 240        val = twl->twl6030_toggle3;
 241        val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
 242        val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
 243
 244        ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
 245        if (ret < 0) {
 246                dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
 247                goto out;
 248        }
 249
 250        twl->twl6030_toggle3 = val;
 251out:
 252        mutex_unlock(&twl->mutex);
 253        return 0;
 254}
 255
 256static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 257{
 258        struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
 259                                                chip);
 260        int ret;
 261        u8 val;
 262
 263        mutex_lock(&twl->mutex);
 264        val = twl->twl6030_toggle3;
 265        val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
 266        val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
 267
 268        ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
 269        if (ret < 0) {
 270                dev_err(chip->dev, "%s: Failed to read TOGGLE3\n", pwm->label);
 271                goto out;
 272        }
 273
 274        val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
 275
 276        ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
 277        if (ret < 0) {
 278                dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
 279                goto out;
 280        }
 281
 282        twl->twl6030_toggle3 = val;
 283out:
 284        mutex_unlock(&twl->mutex);
 285}
 286
 287static const struct pwm_ops twl4030_pwm_ops = {
 288        .config = twl_pwm_config,
 289        .enable = twl4030_pwm_enable,
 290        .disable = twl4030_pwm_disable,
 291        .request = twl4030_pwm_request,
 292        .free = twl4030_pwm_free,
 293};
 294
 295static const struct pwm_ops twl6030_pwm_ops = {
 296        .config = twl_pwm_config,
 297        .enable = twl6030_pwm_enable,
 298        .disable = twl6030_pwm_disable,
 299};
 300
 301static int twl_pwm_probe(struct platform_device *pdev)
 302{
 303        struct twl_pwm_chip *twl;
 304        int ret;
 305
 306        twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
 307        if (!twl)
 308                return -ENOMEM;
 309
 310        if (twl_class_is_4030())
 311                twl->chip.ops = &twl4030_pwm_ops;
 312        else
 313                twl->chip.ops = &twl6030_pwm_ops;
 314
 315        twl->chip.dev = &pdev->dev;
 316        twl->chip.base = -1;
 317        twl->chip.npwm = 2;
 318
 319        mutex_init(&twl->mutex);
 320
 321        ret = pwmchip_add(&twl->chip);
 322        if (ret < 0)
 323                return ret;
 324
 325        platform_set_drvdata(pdev, twl);
 326
 327        return 0;
 328}
 329
 330static int twl_pwm_remove(struct platform_device *pdev)
 331{
 332        struct twl_pwm_chip *twl = platform_get_drvdata(pdev);
 333
 334        return pwmchip_remove(&twl->chip);
 335}
 336
 337#ifdef CONFIG_OF
 338static struct of_device_id twl_pwm_of_match[] = {
 339        { .compatible = "ti,twl4030-pwm" },
 340        { .compatible = "ti,twl6030-pwm" },
 341        { },
 342};
 343MODULE_DEVICE_TABLE(of, twl_pwm_of_match);
 344#endif
 345
 346static struct platform_driver twl_pwm_driver = {
 347        .driver = {
 348                .name = "twl-pwm",
 349                .of_match_table = of_match_ptr(twl_pwm_of_match),
 350        },
 351        .probe = twl_pwm_probe,
 352        .remove = twl_pwm_remove,
 353};
 354module_platform_driver(twl_pwm_driver);
 355
 356MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
 357MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030");
 358MODULE_ALIAS("platform:twl-pwm");
 359MODULE_LICENSE("GPL");
 360
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.