Signed-off-by: Bill Gatliff <bgat@xxxxxxxxxxxxxxx> --- arch/powerpc/platforms/52xx/mpc52xx_gpt.c | 195 ++++++++++++++++++++++++++++- 1 files changed, 193 insertions(+), 2 deletions(-) diff --git a/arch/powerpc/platforms/52xx/mpc52xx_gpt.c b/arch/powerpc/platforms/52xx/mpc52xx_gpt.c index 6f8ebe1..b9aa4a5 100644 --- a/arch/powerpc/platforms/52xx/mpc52xx_gpt.c +++ b/arch/powerpc/platforms/52xx/mpc52xx_gpt.c @@ -1,6 +1,7 @@ /* * MPC5200 General Purpose Timer device driver * + * Copyright (c) 2010 Bill Gatliff <bgat@xxxxxxxxxxxxxxx> * Copyright (c) 2009 Secret Lab Technologies Ltd. * Copyright (c) 2008 Sascha Hauer <s.hauer@xxxxxxxxxxxxxx>, Pengutronix * @@ -50,6 +51,12 @@ * IO, or it can be an Open Collector (OC) output. At the moment it is the * responsibility of either the bootloader or the platform setup code to set * the output mode. This driver does not change the output mode setting. + * + * To use the PWM function, the following property must be added to + * the device tree node for the gpt device: + * pwm-controller; + * TODO: we could set polarity, initial period and duty cycle, on/off, + * and whatnot inside the dts file... */ #include <linux/device.h> @@ -65,11 +72,12 @@ #include <linux/watchdog.h> #include <linux/miscdevice.h> #include <linux/uaccess.h> +#include <linux/pwm/pwm.h> #include <asm/div64.h> #include <asm/mpc52xx.h> MODULE_DESCRIPTION("Freescale MPC52xx gpt driver"); -MODULE_AUTHOR("Sascha Hauer, Grant Likely, Albrecht DreÃ?"); +MODULE_AUTHOR("Sascha Hauer, Grant Likely, Albrecht DreÃ?, Bill Gatliff"); MODULE_LICENSE("GPL"); /** @@ -95,6 +103,9 @@ struct mpc52xx_gpt_priv { #if defined(CONFIG_GPIOLIB) struct of_gpio_chip of_gc; #endif +#if defined(CONFIG_GENERIC_PWM) + struct pwm_device pwm; +#endif }; LIST_HEAD(mpc52xx_gpt_list); @@ -125,6 +136,10 @@ DEFINE_MUTEX(mpc52xx_gpt_list_mutex); #define MPC52xx_GPT_STATUS_IRQMASK (0x000f) +#define MPC52xx_GPT_PWM_WIDTH_MASK (0xffff0000) +#define MPC52xx_GPT_PWM_PWMOP (0x100) +#define MPC52xx_GPT_PWM_LOAD (0x1) + #define MPC52xx_GPT_CAN_WDT (1 << 0) #define MPC52xx_GPT_IS_WDT (1 << 1) @@ -274,6 +289,182 @@ mpc52xx_gpt_irq_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node) /* --------------------------------------------------------------------- + * PWM API hooks + */ +#if defined(CONFIG_GENERIC_PWM) +static inline struct mpc52xx_gpt_priv *pwm_to_mpc52xx_gpt(const struct pwm_channel *p) +{ + return container_of(p->pwm, struct mpc52xx_gpt_priv, pwm); +} + +static int mpc52xx_gpt_pwm_request(struct pwm_channel *p) +{ + struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p); + + /* TODO: add hooks to prevent conflicts in use */ + p->tick_hz = gpt->ipb_freq; + return 0; +} + +static void mpc52xx_gpt_pwm_free(struct pwm_channel *p) +{ + /* TODO: add hooks to prevent conflicts in use */ +} + +static int __mpc52xx_gpt_pwm_config_period_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p); + u64 prescale, count; + + prescale = (c->period_ticks >> 16) + 1; + count = c->period_ticks; + do_div(count, prescale); + out_be32(&gpt->regs->count, prescale << 16 | count); + + p->period_ticks = count * prescale; + dev_dbg(p->pwm->dev, "prescale %4x count %4x period_ticks %8x\n", + (unsigned int)prescale, (unsigned int)count, + (unsigned int)p->period_ticks); + + return 0; +} + +static int __mpc52xx_gpt_pwm_config_duty_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p); + unsigned long flags; + u64 width = c->duty_ticks; + u32 prescale, count; + + spin_lock_irqsave(&gpt->lock, flags); + + count = in_be32(&gpt->regs->count); + prescale = count >> 16; + count &= 0xffffUL; + + /* TODO: this probably isn't the best place to do a divide... */ + do_div(width, prescale); + + clrsetbits_be32(&gpt->regs->pwm, MPC52xx_GPT_PWM_WIDTH_MASK, + MPC52xx_GPT_PWM_WIDTH_MASK & (width << 16)); + spin_unlock_irqrestore(&gpt->lock, flags); + + p->duty_ticks = width * prescale; + dev_dbg(p->pwm->dev, "prescale %4x count %4x width %4x duty_ticks %8x\n", + (unsigned int)prescale, (unsigned int)count, + (unsigned int)width, (unsigned int)p->duty_ticks); + + return 0; +} + +static int __mpc52xx_gpt_pwm_config_polarity(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p); + unsigned long flags; + + spin_lock_irqsave(&gpt->lock, flags); + if (c->polarity) + setbits32(&gpt->regs->pwm, MPC52xx_GPT_PWM_PWMOP); + else + clrbits32(&gpt->regs->pwm, MPC52xx_GPT_PWM_PWMOP); + spin_unlock_irqrestore(&gpt->lock, flags); + return 0; +} + +static int __mpc52xx_gpt_pwm_start(struct pwm_channel *p) +{ + struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p); + unsigned long flags; + + spin_lock_irqsave(&gpt->lock, flags); + clrsetbits_be32(&gpt->regs->mode, MPC52xx_GPT_MODE_MS_MASK, + MPC52xx_GPT_MODE_MS_PWM); + spin_unlock_irqrestore(&gpt->lock, flags); + return 0; +} + +static int __mpc52xx_gpt_pwm_stop(struct pwm_channel *p) +{ + struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p); + unsigned long flags; + + spin_lock_irqsave(&gpt->lock, flags); + clrbits32(&gpt->regs->mode, MPC52xx_GPT_MODE_MS_MASK); + spin_unlock_irqrestore(&gpt->lock, flags); + return 0; +} + +static int mpc52xx_gpt_pwm_config_nosleep(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + int ret = -EINVAL; + + if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) + if ((ret = __mpc52xx_gpt_pwm_config_period_ticks(p, c))) + goto done; + + if (c->config_mask & PWM_CONFIG_DUTY_TICKS) + if ((ret = __mpc52xx_gpt_pwm_config_duty_ticks(p, c))) + goto done; + + if (c->config_mask & PWM_CONFIG_POLARITY) + if ((ret = __mpc52xx_gpt_pwm_config_polarity(p, c))) + goto done; + + if (c->config_mask & PWM_CONFIG_START) + if ((ret = __mpc52xx_gpt_pwm_start(p))) + goto done; + + if (c->config_mask & PWM_CONFIG_STOP) + if ((ret = __mpc52xx_gpt_pwm_stop(p))) + goto done; + +done: + return ret; +} + +static int mpc52xx_gpt_pwm_config(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + dev_dbg(p->pwm->dev, "config_mask %x\n", c->config_mask); + + if (!mpc52xx_gpt_pwm_config_nosleep(p, c)) + return 0; + + might_sleep(); + + /* TODO: add other API entry points */ + + return -EINVAL; +} + +static void +mpc52xx_gpt_pwm_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node) +{ + if (!of_find_property(node, "pwm-controller", NULL)) + return; + + gpt->pwm.dev = gpt->dev; + gpt->pwm.bus_id = dev_name(gpt->dev); + gpt->pwm.nchan = 1; + gpt->pwm.owner = THIS_MODULE; + + gpt->pwm.request = mpc52xx_gpt_pwm_request; + gpt->pwm.free = mpc52xx_gpt_pwm_free; + gpt->pwm.config_nosleep = mpc52xx_gpt_pwm_config_nosleep; + gpt->pwm.config = mpc52xx_gpt_pwm_config; + + pwm_register(&gpt->pwm); +} +#else +static void +mpc52xx_gpt_pwm_setup(struct mpc52xx_gpt_priv *p, struct device_node *np) {} +#endif + +/* --------------------------------------------------------------------- * GPIOLIB hooks */ #if defined(CONFIG_GPIOLIB) @@ -737,9 +928,9 @@ static int __devinit mpc52xx_gpt_probe(struct of_device *ofdev, } dev_set_drvdata(&ofdev->dev, gpt); - mpc52xx_gpt_gpio_setup(gpt, ofdev->node); mpc52xx_gpt_irq_setup(gpt, ofdev->node); + mpc52xx_gpt_pwm_setup(gpt, ofdev->node); mutex_lock(&mpc52xx_gpt_list_mutex); list_add(&gpt->list, &mpc52xx_gpt_list); -- 1.6.5 -- To unsubscribe from this list: send the line "unsubscribe linux-embedded" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html