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 = to_twl(chip);
 204        int ret;
 205        u8 val, mask;
 206
 207        if (pwm->hwpwm == 1)
 208                mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
 209        else
 210                mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
 211
 212        mutex_lock(&twl->mutex);
 213        ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
 214        if (ret < 0) {
 215                dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
 216                goto out;
 217        }
 218
 219        /* Restore the MUX configuration for the PWM */
 220        val &= ~mask;
 221        val |= (twl->twl4030_pwm_mux & mask);
 222
 223        ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
 224        if (ret < 0)
 225                dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label);
 226
 227out:
 228        mutex_unlock(&twl->mutex);
 229}
 230
 231static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 232{
 233        struct twl_pwm_chip *twl = to_twl(chip);
 234        int ret;
 235        u8 val;
 236
 237        mutex_lock(&twl->mutex);
 238        val = twl->twl6030_toggle3;
 239        val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
 240        val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
 241
 242        ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
 243        if (ret < 0) {
 244                dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
 245                goto out;
 246        }
 247
 248        twl->twl6030_toggle3 = val;
 249out:
 250        mutex_unlock(&twl->mutex);
 251        return ret;
 252}
 253
 254static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 255{
 256        struct twl_pwm_chip *twl = to_twl(chip);
 257        int ret;
 258        u8 val;
 259
 260        mutex_lock(&twl->mutex);
 261        val = twl->twl6030_toggle3;
 262        val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
 263        val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
 264
 265        ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
 266        if (ret < 0) {
 267                dev_err(chip->dev, "%s: Failed to read TOGGLE3\n", pwm->label);
 268                goto out;
 269        }
 270
 271        val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
 272
 273        ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
 274        if (ret < 0) {
 275                dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
 276                goto out;
 277        }
 278
 279        twl->twl6030_toggle3 = val;
 280out:
 281        mutex_unlock(&twl->mutex);
 282}
 283
 284static const struct pwm_ops twl4030_pwm_ops = {
 285        .config = twl_pwm_config,
 286        .enable = twl4030_pwm_enable,
 287        .disable = twl4030_pwm_disable,
 288        .request = twl4030_pwm_request,
 289        .free = twl4030_pwm_free,
 290        .owner = THIS_MODULE,
 291};
 292
 293static const struct pwm_ops twl6030_pwm_ops = {
 294        .config = twl_pwm_config,
 295        .enable = twl6030_pwm_enable,
 296        .disable = twl6030_pwm_disable,
 297        .owner = THIS_MODULE,
 298};
 299
 300static int twl_pwm_probe(struct platform_device *pdev)
 301{
 302        struct twl_pwm_chip *twl;
 303        int ret;
 304
 305        twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
 306        if (!twl)
 307                return -ENOMEM;
 308
 309        if (twl_class_is_4030())
 310                twl->chip.ops = &twl4030_pwm_ops;
 311        else
 312                twl->chip.ops = &twl6030_pwm_ops;
 313
 314        twl->chip.dev = &pdev->dev;
 315        twl->chip.base = -1;
 316        twl->chip.npwm = 2;
 317        twl->chip.can_sleep = true;
 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 const 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.