382 lines
8.1 KiB
C
382 lines
8.1 KiB
C
/* linux/arch/arm/mach-s3c64xx/pwm.c
|
|
*
|
|
* (c) 2003-2005 Simtec Electronics
|
|
* Ben Dooks <ben@simtec.co.uk>
|
|
*
|
|
* S3C64XX PWM core
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* Changelog:
|
|
* This file is based on the Sangwook Lee/Samsung patches, re-written due
|
|
* to various ommisions from the code (such as flexible pwm configuration)
|
|
* for use with the BAST system board.
|
|
*
|
|
*
|
|
*/
|
|
#include <linux/errno.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/input.h>
|
|
#include <linux/init.h>
|
|
#include <linux/serio.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/mutex.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/hardware.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <asm/arch/regs-timer.h>
|
|
#include <asm/arch/regs-gpio.h>
|
|
#include <asm/arch/irqs.h>
|
|
#include "pwm-s3c2443.h"
|
|
|
|
|
|
s3c2443_pwm_chan_t s3c_chans[S3C_PWM_CHANNELS];
|
|
|
|
|
|
static inline void
|
|
s3c2443_pwm_buffdone(s3c2443_pwm_chan_t *chan, void *dev)
|
|
{
|
|
|
|
if (chan->callback_fn != NULL) {
|
|
(chan->callback_fn)( dev);
|
|
}
|
|
}
|
|
|
|
|
|
static int s3c2443_pwm_start (int channel)
|
|
{
|
|
unsigned long tcon;
|
|
tcon = __raw_readl(S3C2410_TCON);
|
|
switch(channel)
|
|
{
|
|
case 0:
|
|
tcon |= S3C2410_TCON_T0START;
|
|
tcon &= ~S3C2410_TCON_T0MANUALUPD;
|
|
break;
|
|
case 1:
|
|
tcon |= S3C2410_TCON_T1START;
|
|
tcon &= ~S3C2410_TCON_T1MANUALUPD;
|
|
break;
|
|
case 2:
|
|
tcon |= S3C2410_TCON_T2START;
|
|
tcon &= ~S3C2410_TCON_T2MANUALUPD;
|
|
break;
|
|
case 3:
|
|
tcon |= S3C2410_TCON_T3START;
|
|
tcon &= ~S3C2410_TCON_T3MANUALUPD;
|
|
break;
|
|
|
|
}
|
|
__raw_writel(tcon, S3C2410_TCON);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int s3c2443_timer_setup (int channel, int usec, unsigned long g_tcnt, unsigned long g_tcmp)
|
|
{
|
|
unsigned long tcon;
|
|
unsigned long tcnt;
|
|
unsigned long tcmp;
|
|
unsigned long tcfg1;
|
|
unsigned long tcfg0;
|
|
unsigned long pclk;
|
|
struct clk *clk;
|
|
|
|
printk("\n\nPWM channel %d set g_tcnt = %ld, g_tcmp = %ld \n\n", channel, g_tcnt, g_tcmp);
|
|
|
|
tcnt = 0xffffffff; /* default value for tcnt */
|
|
|
|
/* read the current timer configuration bits */
|
|
tcon = __raw_readl(S3C2410_TCON);
|
|
tcfg1 = __raw_readl(S3C2410_TCFG1);
|
|
tcfg0 = __raw_readl(S3C2410_TCFG0);
|
|
|
|
clk = clk_get(NULL, "timers");
|
|
if (IS_ERR(clk))
|
|
panic("failed to get clock for pwm timer");
|
|
|
|
clk_enable(clk);
|
|
|
|
pclk = clk_get_rate(clk);
|
|
|
|
/* configure clock tick */
|
|
|
|
switch(channel)
|
|
{
|
|
case 0:
|
|
/* set gpio as PWM TIMER0 to signal output*/
|
|
s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_TOUT0);
|
|
s3c2410_gpio_setpin(S3C2410_GPB0, 0);
|
|
tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK;
|
|
tcfg1 |= S3C2410_TCFG1_MUX0_DIV2;
|
|
|
|
tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK;
|
|
tcfg0 |= (4) << 0;
|
|
tcon &= ~(7<<0);
|
|
tcon |= S3C2410_TCON_T0RELOAD;
|
|
break;
|
|
|
|
case 1:
|
|
/* set gpio as PWM TIMER1 to signal output*/
|
|
s3c2410_gpio_cfgpin(S3C2410_GPB1, S3C2410_GPB1_TOUT1);
|
|
s3c2410_gpio_setpin(S3C2410_GPB1, 0);
|
|
tcfg1 &= ~S3C2410_TCFG1_MUX1_MASK;
|
|
tcfg1 |= S3C2410_TCFG1_MUX1_DIV2;
|
|
|
|
tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK;
|
|
tcfg0 |= (4) << 0;
|
|
|
|
tcon &= ~(7<<8);
|
|
tcon |= S3C2410_TCON_T1RELOAD;
|
|
break;
|
|
case 2:
|
|
s3c2410_gpio_cfgpin(S3C2410_GPB2, S3C2410_GPB2_TOUT2);
|
|
s3c2410_gpio_setpin(S3C2410_GPB2, 0);
|
|
tcfg1 &= ~S3C2410_TCFG1_MUX2_MASK;
|
|
tcfg1 |= S3C2410_TCFG1_MUX2_DIV2;
|
|
|
|
tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK;
|
|
tcfg0 |= (4) << S3C2410_TCFG_PRESCALER1_SHIFT;
|
|
|
|
tcon &= ~(7<<12);
|
|
tcon |= S3C2410_TCON_T2RELOAD;
|
|
break;
|
|
case 3:
|
|
s3c2410_gpio_cfgpin(S3C2410_GPB3, S3C2410_GPB3_TOUT3);
|
|
s3c2410_gpio_setpin(S3C2410_GPB3, 0);
|
|
tcfg1 &= ~S3C2410_TCFG1_MUX3_MASK;
|
|
tcfg1 |= S3C2410_TCFG1_MUX3_DIV2;
|
|
|
|
tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK;
|
|
tcfg0 |= (4) << S3C2410_TCFG_PRESCALER1_SHIFT;
|
|
tcon &= ~(7<<16);
|
|
tcon |= S3C2410_TCON_T3RELOAD;
|
|
break;
|
|
}
|
|
|
|
#if 0
|
|
|
|
tcnt = (pclk / ((PRESCALER)*DIVIDER)) / usec;
|
|
|
|
printk("s3c6400 pwm timer tcnt=0x%08lx, pclk=0x%08lx, PRESCALER=%d, DIVIDER=%d, usec=%d\n",
|
|
tcnt, pclk, PRESCALER, DIVIDER, usec);
|
|
|
|
/* timers reload after counting zero, so reduce the count by 1 */
|
|
|
|
tcnt--;
|
|
#endif
|
|
|
|
__raw_writel(tcfg1, S3C2410_TCFG1);
|
|
__raw_writel(tcfg0, S3C2410_TCFG0);
|
|
|
|
|
|
__raw_writel(tcon, S3C2410_TCON);
|
|
tcnt = 160;
|
|
if (tcnt > 0xffffffff) {
|
|
panic("setup_timer: HZ is too small, cannot configure timer!");
|
|
return 0;
|
|
}
|
|
__raw_writel(tcnt, S3C2410_TCNTB(channel));
|
|
tcmp = 110;
|
|
__raw_writel(tcmp, S3C2410_TCMPB(channel));
|
|
|
|
switch(channel)
|
|
{
|
|
case 0:
|
|
tcon |= S3C2410_TCON_T0MANUALUPD;
|
|
break;
|
|
case 1:
|
|
tcon |= S3C2410_TCON_T1MANUALUPD;
|
|
break;
|
|
case 2:
|
|
tcon |= S3C2410_TCON_T2MANUALUPD;
|
|
break;
|
|
case 3:
|
|
tcon |= S3C2410_TCON_T3MANUALUPD;
|
|
break;
|
|
}
|
|
__raw_writel(tcon, S3C2410_TCON);
|
|
|
|
tcnt = g_tcnt;
|
|
__raw_writel(tcnt, S3C2410_TCNTB(channel));
|
|
|
|
tcmp = g_tcmp;
|
|
__raw_writel(tcmp, S3C2410_TCMPB(channel));
|
|
|
|
/* start the timer running */
|
|
|
|
s3c2443_pwm_start (channel);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static irqreturn_t s3c2443_pwm_irq(int irq, void *devpw)
|
|
{
|
|
s3c2443_pwm_chan_t *chan = (s3c2443_pwm_chan_t *)devpw;
|
|
void *dev=chan->dev;
|
|
|
|
/* modify the channel state */
|
|
s3c2443_pwm_buffdone(chan, dev);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
int s3c2443_pwm_request(pwmch_t channel, s3c_pwm_client_t *client, void *dev)
|
|
{
|
|
s3c2443_pwm_chan_t *chan = &s3c_chans[channel];
|
|
unsigned long flags;
|
|
int err;
|
|
|
|
pr_debug("pwm%d: s3c_request_pwm: client=%s, dev=%p\n",
|
|
channel, client->name, dev);
|
|
|
|
|
|
local_irq_save(flags);
|
|
|
|
|
|
if (chan->in_use) {
|
|
if (client != chan->client) {
|
|
printk(KERN_ERR "pwm%d: already in use\n", channel);
|
|
local_irq_restore(flags);
|
|
return -EBUSY;
|
|
} else {
|
|
printk(KERN_ERR "pwm%d: client already has channel\n", channel);
|
|
}
|
|
}
|
|
|
|
chan->client = client;
|
|
chan->in_use = 1;
|
|
chan->dev = dev;
|
|
|
|
if (!chan->irq_claimed) {
|
|
pr_debug("pwm%d: %s : requesting irq %d\n",
|
|
channel, __FUNCTION__, chan->irq);
|
|
|
|
err = request_irq(chan->irq, s3c2443_pwm_irq, SA_INTERRUPT,
|
|
client->name, (void *)chan);
|
|
|
|
if (err) {
|
|
chan->in_use = 0;
|
|
local_irq_restore(flags);
|
|
|
|
printk(KERN_ERR "%s: cannot get IRQ %d for PWM %d\n",
|
|
client->name, chan->irq, chan->number);
|
|
return err;
|
|
}
|
|
|
|
chan->irq_claimed = 1;
|
|
chan->irq_enabled = 1;
|
|
}
|
|
|
|
local_irq_restore(flags);
|
|
|
|
/* need to setup */
|
|
|
|
pr_debug("%s: channel initialised, %p\n", __FUNCTION__, chan);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int s3c2443_pwm_free (pwmch_t channel, s3c_pwm_client_t *client)
|
|
{
|
|
s3c2443_pwm_chan_t *chan = &s3c_chans[channel];
|
|
unsigned long flags;
|
|
|
|
|
|
local_irq_save(flags);
|
|
|
|
if (chan->client != client) {
|
|
printk(KERN_WARNING "pwm%d: possible free from different client (channel %p, passed %p)\n",
|
|
channel, chan->client, client);
|
|
}
|
|
|
|
/* sort out stopping and freeing the channel */
|
|
|
|
|
|
chan->client = NULL;
|
|
chan->in_use = 0;
|
|
|
|
if (chan->irq_claimed)
|
|
free_irq(chan->irq, (void *)chan);
|
|
chan->irq_claimed = 0;
|
|
|
|
local_irq_restore(flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int s3c2443_pwm_set_buffdone_fn(pwmch_t channel, s3c_pwm_cbfn_t rtn)
|
|
{
|
|
s3c2443_pwm_chan_t *chan = &s3c_chans[channel];
|
|
|
|
|
|
pr_debug("%s: chan=%d, callback rtn=%p\n", __FUNCTION__, channel, rtn);
|
|
|
|
chan->callback_fn = rtn;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define s3c2443_pwm_suspend NULL
|
|
#define s3c2443_pwm_resume NULL
|
|
|
|
struct sysdev_class pwm_sysclass = {
|
|
set_kset_name("s3c-pwm"),
|
|
.suspend = s3c2443_pwm_suspend,
|
|
.resume = s3c2443_pwm_resume,
|
|
};
|
|
|
|
|
|
/* initialisation code */
|
|
|
|
static int __init s3c2443_init_pwm(void)
|
|
{
|
|
s3c2443_pwm_chan_t *cp;
|
|
int channel;
|
|
int ret;
|
|
|
|
printk("S3C PWM Driver, (c) 2006-2007 Samsung Electronics\n");
|
|
|
|
ret = sysdev_class_register(&pwm_sysclass);
|
|
if (ret != 0) {
|
|
printk(KERN_ERR "pwm sysclass registration failed\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
for (channel = 0; channel < S3C_PWM_CHANNELS; channel++) {
|
|
cp = &s3c_chans[channel];
|
|
|
|
memset(cp, 0, sizeof(s3c2443_pwm_chan_t));
|
|
|
|
cp->number = channel;
|
|
/* pwm channel irqs are in order.. */
|
|
cp->irq = channel + IRQ_TIMER0;
|
|
|
|
/* register system device */
|
|
|
|
ret = sysdev_register(&cp->sysdev);
|
|
|
|
pr_debug("PWM channel %d , irq %d\n",
|
|
cp->number, cp->irq);
|
|
}
|
|
|
|
// s3c2443_timer_setup(3,10,1000,200);
|
|
return ret;
|
|
}
|
|
__initcall(s3c2443_init_pwm);
|