blob: 153c41284ab716b6f321912ec1ddb9df4415365e [file] [log] [blame]
/*****************************************************************************
* Copyright 2006 - 2011 Broadcom Corporation. All rights reserved.
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2, available at
* http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
*
* Notwithstanding the above, under no circumstances may you combine this
* software in any way with any other Broadcom software provided under a
* license other than the GPL, without Broadcom's express prior written
* consent.
*****************************************************************************/
/*
* Frameworks:
*
* - SMP:
* - GPIO:
* - MMU: Fully supported. Platform model with ioremap used (mostly).
* - Dynamic /dev:
* - Suspend:
* - Clocks: Not done. Awaiting clock framework to be completed.
* - Power: Not done.
*
*/
/*
* KONA PWM driver
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/completion.h>
#include <linux/pwm.h>
#include <mach/rdb/brcm_rdb_pwm_top.h>
#define KONA_PWM_CHANNEL_CNT 6
#define PWM_PRESCALER_MAX 7
struct kona_pwmc {
struct pwm_device *p[KONA_PWM_CHANNEL_CNT];
struct pwm_device_ops ops;
void __iomem *iobase;
struct clk *clk;
int pwm_started;
struct pwm_chip chip;
};
struct pwm_control {
u32 smooth_type_mask;
u32 smooth_type_shift;
u32 pwmout_type_mask;
u32 pwmout_type_shift;
u32 pwmout_polarity_mask;
u32 pwmout_polarity_shift;
u32 pwmout_enable_mask;
u32 pwmout_enable_shift;
u32 offset;
};
struct pwm_reg_def {
u32 mask;
u32 shift;
u32 offset;
};
#define PWM_CONTROL_PROP(chan, stm, sts, ptm, pts, pm, ps, em, es, addr) \
[chan] = { \
.smooth_type_mask = stm, \
.smooth_type_shift = sts, \
.pwmout_type_mask = ptm, \
.pwmout_type_shift = pts, \
.pwmout_polarity_mask = pm, \
.pwmout_polarity_shift = ps, \
.pwmout_enable_mask = em, \
.pwmout_enable_shift = es, \
.offset = addr \
}
#define CHAN_SHIFT(shift,chan) (shift+chan)
#define CHAN_MASK(mask,shift,chan) (mask & ( 1 << CHAN_SHIFT(shift,chan) ) )
#define PWM_CONTROL_SET(chan) \
PWM_CONTROL_PROP( chan , \
CHAN_MASK(PWM_TOP_PWM_CONTROL_SMOOTH_TYPE_MASK,PWM_TOP_PWM_CONTROL_SMOOTH_TYPE_SHIFT,chan), \
CHAN_SHIFT(PWM_TOP_PWM_CONTROL_SMOOTH_TYPE_SHIFT,chan), \
CHAN_MASK(PWM_TOP_PWM_CONTROL_PWMOUT_TYPE_MASK,PWM_TOP_PWM_CONTROL_PWMOUT_TYPE_SHIFT,chan), \
CHAN_SHIFT(PWM_TOP_PWM_CONTROL_PWMOUT_TYPE_SHIFT,chan), \
CHAN_MASK(PWM_TOP_PWM_CONTROL_PWMOUT_POLARITY_MASK,PWM_TOP_PWM_CONTROL_PWMOUT_POLARITY_SHIFT,chan), \
CHAN_SHIFT(PWM_TOP_PWM_CONTROL_PWMOUT_POLARITY_SHIFT,chan), \
CHAN_MASK(PWM_TOP_PWM_CONTROL_PWMOUT_ENABLE_MASK,PWM_TOP_PWM_CONTROL_PWMOUT_ENABLE_SHIFT,chan), \
CHAN_SHIFT(PWM_TOP_PWM_CONTROL_PWMOUT_ENABLE_SHIFT,chan), \
PWM_TOP_PWM_CONTROL_OFFSET \
)
#define PWM_REG_DEF(c, m, s, a) \
[c] = { \
.mask = m, \
.shift = s, \
.offset = a \
}
static const struct pwm_control pwm_chan_ctrl_info[KONA_PWM_CHANNEL_CNT] = {
PWM_CONTROL_SET(0),
PWM_CONTROL_SET(1),
PWM_CONTROL_SET(2),
PWM_CONTROL_SET(3),
PWM_CONTROL_SET(4),
PWM_CONTROL_SET(5)
};
static const struct pwm_reg_def pwm_chan_pre_scaler_info[KONA_PWM_CHANNEL_CNT] = {
PWM_REG_DEF(0, PWM_TOP_PRESCALE_CONTROL_PWM0_PRESCALE_MASK,
PWM_TOP_PRESCALE_CONTROL_PWM0_PRESCALE_SHIFT,
PWM_TOP_PRESCALE_CONTROL_OFFSET),
PWM_REG_DEF(1, PWM_TOP_PRESCALE_CONTROL_PWM1_PRESCALE_MASK,
PWM_TOP_PRESCALE_CONTROL_PWM1_PRESCALE_SHIFT,
PWM_TOP_PRESCALE_CONTROL_OFFSET),
PWM_REG_DEF(2, PWM_TOP_PRESCALE_CONTROL_PWM2_PRESCALE_MASK,
PWM_TOP_PRESCALE_CONTROL_PWM2_PRESCALE_SHIFT,
PWM_TOP_PRESCALE_CONTROL_OFFSET),
PWM_REG_DEF(3, PWM_TOP_PRESCALE_CONTROL_PWM3_PRESCALE_MASK,
PWM_TOP_PRESCALE_CONTROL_PWM3_PRESCALE_SHIFT,
PWM_TOP_PRESCALE_CONTROL_OFFSET),
PWM_REG_DEF(4, PWM_TOP_PRESCALE_CONTROL_PWM4_PRESCALE_MASK,
PWM_TOP_PRESCALE_CONTROL_PWM4_PRESCALE_SHIFT,
PWM_TOP_PRESCALE_CONTROL_OFFSET),
PWM_REG_DEF(5, PWM_TOP_PRESCALE_CONTROL_PWM5_PRESCALE_MASK,
PWM_TOP_PRESCALE_CONTROL_PWM5_PRESCALE_SHIFT,
PWM_TOP_PRESCALE_CONTROL_OFFSET),
};
static const struct pwm_reg_def pwm_chan_period_cnt_info[KONA_PWM_CHANNEL_CNT] = {
PWM_REG_DEF(0, PWM_TOP_PWM0_PERIOD_COUNT_PWM0_CNT_MASK,
PWM_TOP_PWM0_PERIOD_COUNT_PWM0_CNT_SHIFT,
PWM_TOP_PWM0_PERIOD_COUNT_OFFSET),
PWM_REG_DEF(1, PWM_TOP_PWM1_PERIOD_COUNT_PWM1_CNT_MASK,
PWM_TOP_PWM1_PERIOD_COUNT_PWM1_CNT_SHIFT,
PWM_TOP_PWM1_PERIOD_COUNT_OFFSET),
PWM_REG_DEF(2, PWM_TOP_PWM2_PERIOD_COUNT_PWM2_CNT_MASK,
PWM_TOP_PWM2_PERIOD_COUNT_PWM2_CNT_SHIFT,
PWM_TOP_PWM2_PERIOD_COUNT_OFFSET),
PWM_REG_DEF(3, PWM_TOP_PWM3_PERIOD_COUNT_PWM3_CNT_MASK,
PWM_TOP_PWM3_PERIOD_COUNT_PWM3_CNT_SHIFT,
PWM_TOP_PWM3_PERIOD_COUNT_OFFSET),
PWM_REG_DEF(4, PWM_TOP_PWM4_PERIOD_COUNT_PWM4_CNT_MASK,
PWM_TOP_PWM4_PERIOD_COUNT_PWM4_CNT_SHIFT,
PWM_TOP_PWM4_PERIOD_COUNT_OFFSET),
PWM_REG_DEF(5, PWM_TOP_PWM5_PERIOD_COUNT_PWM5_CNT_MASK,
PWM_TOP_PWM5_PERIOD_COUNT_PWM5_CNT_SHIFT,
PWM_TOP_PWM5_PERIOD_COUNT_OFFSET),
};
static const struct pwm_reg_def pwm_chan_duty_cycle_info[KONA_PWM_CHANNEL_CNT] = {
PWM_REG_DEF(0, PWM_TOP_PWM0_DUTY_CYCLE_HIGH_PWM0_HIGH_MASK,
PWM_TOP_PWM0_DUTY_CYCLE_HIGH_PWM0_HIGH_SHIFT,
PWM_TOP_PWM0_DUTY_CYCLE_HIGH_OFFSET),
PWM_REG_DEF(1, PWM_TOP_PWM1_DUTY_CYCLE_HIGH_PWM1_HIGH_MASK,
PWM_TOP_PWM1_DUTY_CYCLE_HIGH_PWM1_HIGH_SHIFT,
PWM_TOP_PWM1_DUTY_CYCLE_HIGH_OFFSET),
PWM_REG_DEF(2, PWM_TOP_PWM2_DUTY_CYCLE_HIGH_PWM2_HIGH_MASK,
PWM_TOP_PWM2_DUTY_CYCLE_HIGH_PWM2_HIGH_SHIFT,
PWM_TOP_PWM2_DUTY_CYCLE_HIGH_OFFSET),
PWM_REG_DEF(3, PWM_TOP_PWM3_DUTY_CYCLE_HIGH_PWM3_HIGH_MASK,
PWM_TOP_PWM3_DUTY_CYCLE_HIGH_PWM3_HIGH_SHIFT,
PWM_TOP_PWM3_DUTY_CYCLE_HIGH_OFFSET),
PWM_REG_DEF(4, PWM_TOP_PWM4_DUTY_CYCLE_HIGH_PWM4_HIGH_MASK,
PWM_TOP_PWM4_DUTY_CYCLE_HIGH_PWM4_HIGH_SHIFT,
PWM_TOP_PWM4_DUTY_CYCLE_HIGH_OFFSET),
PWM_REG_DEF(5, PWM_TOP_PWM5_DUTY_CYCLE_HIGH_PWM5_HIGH_MASK,
PWM_TOP_PWM5_DUTY_CYCLE_HIGH_PWM5_HIGH_SHIFT,
PWM_TOP_PWM5_DUTY_CYCLE_HIGH_OFFSET),
};
static int kona_get_chan(const struct kona_pwmc *ap, const struct pwm_device *p)
{
int chan;
for (chan = 0; chan < KONA_PWM_CHANNEL_CNT; chan++)
if (p == ap->p[chan])
return chan;
BUG();
return 0;
}
static void kona_pwmc_clear_set_bit(const struct kona_pwmc *ap,
unsigned int offset, unsigned int shift,
unsigned char en_dis)
{
unsigned long val = readl(ap->iobase + offset);
// Clear bit.
clear_bit(shift, &val);
if (en_dis == 1)
set_bit(shift, &val);
writel(val, (ap->iobase + offset));
}
static void kona_pwmc_set_field(const struct kona_pwmc *ap, unsigned int offset,
unsigned int mask, unsigned int shift,
unsigned int wval)
{
unsigned int val = readl(ap->iobase + offset);
val = (val & ~mask) | (wval << shift);
writel(val, (ap->iobase + offset));
}
static void kona_pwmc_get_field(const struct kona_pwmc *ap, unsigned int offset,
unsigned int mask, unsigned int shift,
unsigned int *val)
{
*val = readl(ap->iobase + offset);
*val = (*val & mask) >> shift;
}
static void kona_pwmc_stop(const struct kona_pwmc *ap, int chan)
{
kona_pwmc_clear_set_bit(ap, pwm_chan_ctrl_info[chan].offset,
pwm_chan_ctrl_info[chan].pwmout_enable_shift,
0);
}
static void kona_pwmc_start(const struct kona_pwmc *ap, int chan)
{
kona_pwmc_clear_set_bit(ap, pwm_chan_ctrl_info[chan].offset,
pwm_chan_ctrl_info[chan].pwmout_enable_shift,
1);
}
static void kona_pwmc_config_polarity(struct kona_pwmc *ap, int chan,
struct pwm_config *c)
{
struct pwm_device *p = ap->p[chan];
clk_enable(ap->clk);
if (c->polarity)
kona_pwmc_clear_set_bit(ap, pwm_chan_ctrl_info[chan].offset,
pwm_chan_ctrl_info[chan].
pwmout_polarity_shift, 1);
else
kona_pwmc_clear_set_bit(ap, pwm_chan_ctrl_info[chan].offset,
pwm_chan_ctrl_info[chan].
pwmout_polarity_shift, 0);
p->polarity = c->polarity ? 1 : 0;
clk_disable(ap->clk);
}
static void kona_pwmc_config_duty_ticks(struct kona_pwmc *ap, int chan,
struct pwm_config *c)
{
struct pwm_device *p = ap->p[chan];
unsigned int pre_scaler = 0;
unsigned int duty_cnt = 0;
clk_enable(ap->clk);
kona_pwmc_get_field(ap, pwm_chan_pre_scaler_info[chan].offset,
pwm_chan_pre_scaler_info[chan].mask,
pwm_chan_pre_scaler_info[chan].shift, &pre_scaler);
// Read prescaler value from register.
duty_cnt = c->duty_ticks / (pre_scaler + 1);
/* disable channel */
kona_pwmc_stop(ap, chan);
// program duty cycle.
kona_pwmc_set_field(ap, pwm_chan_duty_cycle_info[chan].offset,
pwm_chan_duty_cycle_info[chan].mask,
pwm_chan_duty_cycle_info[chan].shift, duty_cnt);
/* Workaround suggested by ASIC team */
if (pre_scaler >= 2)
ndelay(310);
// enable channel.
kona_pwmc_start(ap, chan);
p->duty_ticks = c->duty_ticks;
clk_disable(ap->clk);
}
static int kona_pwmc_config_period_ticks(struct kona_pwmc *ap, int chan,
struct pwm_config *c)
{
unsigned int pcnt;
unsigned char pre_scaler = 0;
struct pwm_device *p = ap->p[chan];
clk_enable(ap->clk);
// pcnt = ( 26 * 1000000 * period_ns ) / (pre_scaler * 1000000000 )
// Calculate period cnt.
pre_scaler = c->period_ticks / 0xFFFFFF;
if (pre_scaler > PWM_PRESCALER_MAX)
pre_scaler = PWM_PRESCALER_MAX;
pcnt = c->period_ticks / (pre_scaler + 1);
// program prescaler
kona_pwmc_set_field(ap, pwm_chan_pre_scaler_info[chan].offset,
pwm_chan_pre_scaler_info[chan].mask,
pwm_chan_pre_scaler_info[chan].shift, pre_scaler);
// program period count.
kona_pwmc_set_field(ap, pwm_chan_period_cnt_info[chan].offset,
pwm_chan_period_cnt_info[chan].mask,
pwm_chan_period_cnt_info[chan].shift, pcnt);
// disable channel
kona_pwmc_stop(ap, chan);
/* Workaround suggested by ASIC team */
if (pre_scaler >= 2)
ndelay(310);
// enable channel.
kona_pwmc_start(ap, chan);
p->period_ticks = c->period_ticks;
clk_disable(ap->clk);
return 0;
}
static int kona_pwmc_config(struct pwm_device *p, struct pwm_config *c)
{
struct kona_pwmc *ap = pwm_get_drvdata(p);
int chan = kona_get_chan(ap, p);
int ret = 0;
if (test_bit(PWM_CONFIG_POLARITY, &c->config_mask))
kona_pwmc_config_polarity(ap, chan, c);
if (test_bit(PWM_CONFIG_DUTY_TICKS, &c->config_mask))
kona_pwmc_config_duty_ticks(ap, chan, c);
if (test_bit(PWM_CONFIG_PERIOD_TICKS, &c->config_mask)) {
ret = kona_pwmc_config_period_ticks(ap, chan, c);
if (ret)
goto out;
if (!test_bit(PWM_CONFIG_DUTY_TICKS, &c->config_mask)) {
struct pwm_config d = {
.config_mask = PWM_CONFIG_DUTY_TICKS,
.duty_ticks = p->duty_ticks,
};
kona_pwmc_config_duty_ticks(ap, chan, &d);
}
}
if (!ap->pwm_started && test_bit(PWM_CONFIG_START, &c->config_mask)) {
/* Restore duty ticks cater for STOP case. */
struct pwm_config d = {
.config_mask = PWM_CONFIG_DUTY_TICKS,
.duty_ticks = p->duty_ticks,
};
ap->pwm_started = 1;
usleep_range(3000, 7000);
clk_enable(ap->clk);
kona_pwmc_clear_set_bit(ap, pwm_chan_ctrl_info[chan].offset,
pwm_chan_ctrl_info[chan].
smooth_type_shift, 1);
kona_pwmc_config_duty_ticks(ap, chan, &d);
kona_pwmc_start(ap, chan);
}
if (ap->pwm_started && test_bit(PWM_CONFIG_STOP, &c->config_mask)) {
struct pwm_config d = {
.config_mask = PWM_CONFIG_DUTY_TICKS,
.duty_ticks = 0,
};
ap->pwm_started = 0;
kona_pwmc_config_duty_ticks(ap, chan, &d);
kona_pwmc_clear_set_bit(ap, pwm_chan_ctrl_info[chan].offset,
pwm_chan_ctrl_info[chan].
smooth_type_shift, 0);
/* turn-off the PWM clock i.e. enabled during pwm_start */
ndelay(410);
clk_disable(ap->clk);
}
out:
return ret;
}
static int kona_pwmc_request(struct pwm_device *p)
{
struct kona_pwmc *ap = pwm_get_drvdata(p);
int chan = kona_get_chan(ap, p);
clk_enable(ap->clk);
p->tick_hz = clk_get_rate(ap->clk);
kona_pwmc_stop(ap, chan);
clk_disable(ap->clk);
return 0;
}
static void kona_pwmc_release(struct pwm_device *p)
{
}
static const struct pwm_device_ops kona_pwm_ops = {
.request = kona_pwmc_request,
.release = kona_pwmc_release,
.config = kona_pwmc_config,
.owner = THIS_MODULE,
};
static int kona_pwmc_probe(struct platform_device *pdev)
{
struct kona_pwmc *ap;
struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
int chan;
int ret = 0;
ap = kzalloc(sizeof(*ap), GFP_KERNEL);
if (!ap) {
ret = -ENOMEM;
goto err_kona_pwmc_alloc;
}
platform_set_drvdata(pdev, ap);
ap->clk = clk_get(&pdev->dev, "pwm_clk");
if (IS_ERR(ap->clk)) {
ret = -ENODEV;
goto err_clk_get;
}
ap->iobase = ioremap_nocache(r->start, resource_size(r));
if (!ap->iobase) {
ret = -ENODEV;
goto err_ioremap;
}
ap->chip.dev = &pdev->dev;
ap->chip.ops = &kona_pwm_ops;
ap->chip.base = -1;
ap>chip.npwm = KONA_PWM_CHANNEL_CNT;
for (chan = 0; chan < KONA_PWM_CHANNEL_CNT; chan++) {
ap->p[chan] = pwm_register(&kona_pwm_ops, &pdev->dev, "%s:%d",
"kona_pwmc", chan);
if (IS_ERR_OR_NULL(ap->p[chan]))
goto err_pwm_register;
pwm_set_drvdata(ap->p[chan], ap);
kona_pwmc_clear_set_bit(ap, pwm_chan_ctrl_info[chan].offset,
pwm_chan_ctrl_info[chan].
smooth_type_shift, 1);
}
printk(KERN_INFO "PWM: driver initialized properly");
return 0;
err_pwm_register:
while (--chan > 0)
pwm_unregister(ap->p[chan]);
iounmap(ap->iobase);
err_ioremap:
clk_put(ap->clk);
err_clk_get:
platform_set_drvdata(pdev, NULL);
kfree(ap);
err_kona_pwmc_alloc:
printk(KERN_ERR "%s: error, returning %d\n", __func__, ret);
return ret;
}
static int kona_pwmc_remove(struct platform_device *pdev)
{
struct kona_pwmc *ap = platform_get_drvdata(pdev);
int chan;
for (chan = 0; chan < KONA_PWM_CHANNEL_CNT; chan++)
pwm_unregister(ap->p[chan]);
clk_put(ap->clk);
iounmap(ap->iobase);
kfree(ap);
return 0;
}
#ifdef CONFIG_PM
static int kona_pwmc_suspend(struct platform_device *pdev, pm_message_t state)
{
/* TODO: add more resume support in the future */
return 0;
}
static int kona_pwmc_resume(struct platform_device *pdev)
{
/* TODO: add more resume support in the future */
return 0;
}
#else
#define kona_pwmc_suspend NULL
#define kona_pwmc_resume NULL
#endif
static const struct of_device_id kona_pwmc_dt_ids[] = {
{ .compatible = "bcm,pwmc", },
{}
};
static struct platform_driver kona_pwmc_driver = {
.driver = {
.name = "kona_pwmc",
.owner = THIS_MODULE,
.of_match_table = kona_pwmc_dt_ids,
},
.probe = kona_pwmc_probe,
.remove = kona_pwmc_remove,
.suspend = kona_pwmc_suspend,
.resume = kona_pwmc_resume,
};
static const char gBanner[] =
KERN_INFO "Broadcom Pulse Width Modulator Driver: 1.00\n";
static int __init kona_pwmc_init(void)
{
printk(gBanner);
return platform_driver_register(&kona_pwmc_driver);
}
static void __exit kona_pwmc_exit(void)
{
platform_driver_unregister(&kona_pwmc_driver);
}
module_init(kona_pwmc_init);
module_exit(kona_pwmc_exit);
MODULE_AUTHOR("Broadcom");
MODULE_DESCRIPTION("Driver for KONA PWMC");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");