mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-17 10:46:33 +00:00
i2c: S3C24XX I2C frequency scaling support.
Add support for CPU frequency scaling to the S3C24XX I2C driver. Signed-off-by: Ben Dooks <ben-linux@fluff.org>
This commit is contained in:
parent
1efe7c55d2
commit
61c7cff892
@ -33,6 +33,7 @@
|
|||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
|
#include <linux/cpufreq.h>
|
||||||
|
|
||||||
#include <asm/hardware.h>
|
#include <asm/hardware.h>
|
||||||
#include <asm/irq.h>
|
#include <asm/irq.h>
|
||||||
@ -64,6 +65,7 @@ struct s3c24xx_i2c {
|
|||||||
unsigned int tx_setup;
|
unsigned int tx_setup;
|
||||||
|
|
||||||
enum s3c24xx_i2c_state state;
|
enum s3c24xx_i2c_state state;
|
||||||
|
unsigned long clkrate;
|
||||||
|
|
||||||
void __iomem *regs;
|
void __iomem *regs;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
@ -71,6 +73,10 @@ struct s3c24xx_i2c {
|
|||||||
struct resource *irq;
|
struct resource *irq;
|
||||||
struct resource *ioarea;
|
struct resource *ioarea;
|
||||||
struct i2c_adapter adap;
|
struct i2c_adapter adap;
|
||||||
|
|
||||||
|
#ifdef CONFIG_CPU_FREQ
|
||||||
|
struct notifier_block freq_transition;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/* default platform data to use if not supplied in the platform_device
|
/* default platform data to use if not supplied in the platform_device
|
||||||
@ -501,6 +507,9 @@ static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int
|
|||||||
unsigned long timeout;
|
unsigned long timeout;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (!readl(i2c->regs + S3C2410_IICCON) & S3C2410_IICCON_IRQEN)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
ret = s3c24xx_i2c_set_master(i2c);
|
ret = s3c24xx_i2c_set_master(i2c);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
|
dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
|
||||||
@ -636,24 +645,25 @@ static inline int freq_acceptable(unsigned int freq, unsigned int wanted)
|
|||||||
return (diff >= -2 && diff <= 2);
|
return (diff >= -2 && diff <= 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* s3c24xx_i2c_getdivisor
|
/* s3c24xx_i2c_clockrate
|
||||||
*
|
*
|
||||||
* work out a divisor for the user requested frequency setting,
|
* work out a divisor for the user requested frequency setting,
|
||||||
* either by the requested frequency, or scanning the acceptable
|
* either by the requested frequency, or scanning the acceptable
|
||||||
* range of frequencies until something is found
|
* range of frequencies until something is found
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int s3c24xx_i2c_getdivisor(struct s3c24xx_i2c *i2c,
|
static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
|
||||||
struct s3c2410_platform_i2c *pdata,
|
|
||||||
unsigned long *iicon,
|
|
||||||
unsigned int *got)
|
|
||||||
{
|
{
|
||||||
|
struct s3c2410_platform_i2c *pdata;
|
||||||
unsigned long clkin = clk_get_rate(i2c->clk);
|
unsigned long clkin = clk_get_rate(i2c->clk);
|
||||||
|
|
||||||
unsigned int divs, div1;
|
unsigned int divs, div1;
|
||||||
|
u32 iiccon;
|
||||||
int freq;
|
int freq;
|
||||||
int start, end;
|
int start, end;
|
||||||
|
|
||||||
|
i2c->clkrate = clkin;
|
||||||
|
|
||||||
|
pdata = s3c24xx_i2c_get_platformdata(i2c->adap.dev.parent);
|
||||||
clkin /= 1000; /* clkin now in KHz */
|
clkin /= 1000; /* clkin now in KHz */
|
||||||
|
|
||||||
dev_dbg(i2c->dev, "pdata %p, freq %lu %lu..%lu\n",
|
dev_dbg(i2c->dev, "pdata %p, freq %lu %lu..%lu\n",
|
||||||
@ -688,11 +698,79 @@ static int s3c24xx_i2c_getdivisor(struct s3c24xx_i2c *i2c,
|
|||||||
|
|
||||||
found:
|
found:
|
||||||
*got = freq;
|
*got = freq;
|
||||||
*iicon |= (divs-1);
|
|
||||||
*iicon |= (div1 == 512) ? S3C2410_IICCON_TXDIV_512 : 0;
|
iiccon = readl(i2c->regs + S3C2410_IICCON);
|
||||||
|
iiccon &= ~(S3C2410_IICCON_SCALEMASK | S3C2410_IICCON_TXDIV_512);
|
||||||
|
iiccon |= (divs-1);
|
||||||
|
|
||||||
|
if (div1 == 512)
|
||||||
|
iiccon |= S3C2410_IICCON_TXDIV_512;
|
||||||
|
|
||||||
|
writel(iiccon, i2c->regs + S3C2410_IICCON);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_CPU_FREQ
|
||||||
|
|
||||||
|
#define freq_to_i2c(_n) container_of(_n, struct s3c24xx_i2c, freq_transition)
|
||||||
|
|
||||||
|
static int s3c24xx_i2c_cpufreq_transition(struct notifier_block *nb,
|
||||||
|
unsigned long val, void *data)
|
||||||
|
{
|
||||||
|
struct s3c24xx_i2c *i2c = freq_to_i2c(nb);
|
||||||
|
unsigned long flags;
|
||||||
|
unsigned int got;
|
||||||
|
int delta_f;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
delta_f = clk_get_rate(i2c->clk) - i2c->clkrate;
|
||||||
|
|
||||||
|
/* if we're post-change and the input clock has slowed down
|
||||||
|
* or at pre-change and the clock is about to speed up, then
|
||||||
|
* adjust our clock rate. <0 is slow, >0 speedup.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((val == CPUFREQ_POSTCHANGE && delta_f < 0) ||
|
||||||
|
(val == CPUFREQ_PRECHANGE && delta_f > 0)) {
|
||||||
|
spin_lock_irqsave(&i2c->lock, flags);
|
||||||
|
ret = s3c24xx_i2c_clockrate(i2c, &got);
|
||||||
|
spin_unlock_irqrestore(&i2c->lock, flags);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
dev_err(i2c->dev, "cannot find frequency\n");
|
||||||
|
else
|
||||||
|
dev_info(i2c->dev, "setting freq %d\n", got);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int s3c24xx_i2c_register_cpufreq(struct s3c24xx_i2c *i2c)
|
||||||
|
{
|
||||||
|
i2c->freq_transition.notifier_call = s3c24xx_i2c_cpufreq_transition;
|
||||||
|
|
||||||
|
return cpufreq_register_notifier(&i2c->freq_transition,
|
||||||
|
CPUFREQ_TRANSITION_NOTIFIER);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void s3c24xx_i2c_deregister_cpufreq(struct s3c24xx_i2c *i2c)
|
||||||
|
{
|
||||||
|
cpufreq_unregister_notifier(&i2c->freq_transition,
|
||||||
|
CPUFREQ_TRANSITION_NOTIFIER);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
static inline int s3c24xx_i2c_register_cpufreq(struct s3c24xx_i2c *i2c)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void s3c24xx_i2c_deregister_cpufreq(struct s3c24xx_i2c *i2c)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* s3c24xx_i2c_init
|
/* s3c24xx_i2c_init
|
||||||
*
|
*
|
||||||
* initialise the controller, set the IO lines and frequency
|
* initialise the controller, set the IO lines and frequency
|
||||||
@ -719,9 +797,12 @@ static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
|
|||||||
|
|
||||||
dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);
|
dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);
|
||||||
|
|
||||||
|
writel(iicon, i2c->regs + S3C2410_IICCON);
|
||||||
|
|
||||||
/* we need to work out the divisors for the clock... */
|
/* we need to work out the divisors for the clock... */
|
||||||
|
|
||||||
if (s3c24xx_i2c_getdivisor(i2c, pdata, &iicon, &freq) != 0) {
|
if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
|
||||||
|
writel(0, i2c->regs + S3C2410_IICCON);
|
||||||
dev_err(i2c->dev, "cannot meet bus frequency required\n");
|
dev_err(i2c->dev, "cannot meet bus frequency required\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
@ -731,8 +812,6 @@ static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
|
|||||||
dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
|
dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
|
||||||
dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);
|
dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);
|
||||||
|
|
||||||
writel(iicon, i2c->regs + S3C2410_IICCON);
|
|
||||||
|
|
||||||
/* check for s3c2440 i2c controller */
|
/* check for s3c2440 i2c controller */
|
||||||
|
|
||||||
if (s3c24xx_i2c_is2440(i2c)) {
|
if (s3c24xx_i2c_is2440(i2c)) {
|
||||||
@ -835,6 +914,12 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev)
|
|||||||
dev_dbg(&pdev->dev, "irq resource %p (%lu)\n", res,
|
dev_dbg(&pdev->dev, "irq resource %p (%lu)\n", res,
|
||||||
(unsigned long)res->start);
|
(unsigned long)res->start);
|
||||||
|
|
||||||
|
ret = s3c24xx_i2c_register_cpufreq(i2c);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
|
||||||
|
goto err_irq;
|
||||||
|
}
|
||||||
|
|
||||||
/* Note, previous versions of the driver used i2c_add_adapter()
|
/* Note, previous versions of the driver used i2c_add_adapter()
|
||||||
* to add the bus at any number. We now pass the bus number via
|
* to add the bus at any number. We now pass the bus number via
|
||||||
* the platform data, so if unset it will now default to always
|
* the platform data, so if unset it will now default to always
|
||||||
@ -846,7 +931,7 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev)
|
|||||||
ret = i2c_add_numbered_adapter(&i2c->adap);
|
ret = i2c_add_numbered_adapter(&i2c->adap);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
|
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
|
||||||
goto err_irq;
|
goto err_cpufreq;
|
||||||
}
|
}
|
||||||
|
|
||||||
platform_set_drvdata(pdev, i2c);
|
platform_set_drvdata(pdev, i2c);
|
||||||
@ -854,6 +939,9 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev)
|
|||||||
dev_info(&pdev->dev, "%s: S3C I2C adapter\n", i2c->adap.dev.bus_id);
|
dev_info(&pdev->dev, "%s: S3C I2C adapter\n", i2c->adap.dev.bus_id);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err_cpufreq:
|
||||||
|
s3c24xx_i2c_deregister_cpufreq(i2c);
|
||||||
|
|
||||||
err_irq:
|
err_irq:
|
||||||
free_irq(i2c->irq->start, i2c);
|
free_irq(i2c->irq->start, i2c);
|
||||||
|
|
||||||
@ -881,6 +969,8 @@ static int s3c24xx_i2c_remove(struct platform_device *pdev)
|
|||||||
{
|
{
|
||||||
struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);
|
struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
s3c24xx_i2c_deregister_cpufreq(i2c);
|
||||||
|
|
||||||
i2c_del_adapter(&i2c->adap);
|
i2c_del_adapter(&i2c->adap);
|
||||||
free_irq(i2c->irq->start, i2c);
|
free_irq(i2c->irq->start, i2c);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user