Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 61c7cff8 authored by Ben Dooks's avatar Ben Dooks
Browse files

i2c: S3C24XX I2C frequency scaling support.



Add support for CPU frequency scaling to the S3C24XX I2C driver.

Signed-off-by: default avatarBen Dooks <ben-linux@fluff.org>
parent 1efe7c55
Loading
Loading
Loading
Loading
+103 −13
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>

#include <asm/hardware.h>
#include <asm/irq.h>
@@ -64,6 +65,7 @@ struct s3c24xx_i2c {
	unsigned int		tx_setup;

	enum s3c24xx_i2c_state	state;
	unsigned long		clkrate;

	void __iomem		*regs;
	struct clk		*clk;
@@ -71,6 +73,10 @@ struct s3c24xx_i2c {
	struct resource		*irq;
	struct resource		*ioarea;
	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
@@ -501,6 +507,9 @@ static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int
	unsigned long timeout;
	int ret;

	if (!readl(i2c->regs + S3C2410_IICCON) & S3C2410_IICCON_IRQEN)
		return -EIO;

	ret = s3c24xx_i2c_set_master(i2c);
	if (ret != 0) {
		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);
}

/* s3c24xx_i2c_getdivisor
/* s3c24xx_i2c_clockrate
 *
 * work out a divisor for the user requested frequency setting,
 * either by the requested frequency, or scanning the acceptable
 * range of frequencies until something is found
*/

static int s3c24xx_i2c_getdivisor(struct s3c24xx_i2c *i2c,
				  struct s3c2410_platform_i2c *pdata,
				  unsigned long *iicon,
				  unsigned int *got)
static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
{
	struct s3c2410_platform_i2c *pdata;
	unsigned long clkin = clk_get_rate(i2c->clk);
	
	unsigned int divs, div1;
	u32 iiccon;
	int freq;
	int start, end;

	i2c->clkrate = clkin;

	pdata = s3c24xx_i2c_get_platformdata(i2c->adap.dev.parent);
	clkin /= 1000;		/* clkin now in KHz */
     
	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:
	*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;
}

#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
 *
 * 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);

	writel(iicon, i2c->regs + S3C2410_IICCON);

	/* 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");
		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_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);

	writel(iicon, i2c->regs + S3C2410_IICCON);

	/* check for s3c2440 i2c controller  */

	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,
		(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()
	 * 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
@@ -846,7 +931,7 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev)
	ret = i2c_add_numbered_adapter(&i2c->adap);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to add bus to i2c core\n");
		goto err_irq;
		goto err_cpufreq;
	}

	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);
	return 0;

 err_cpufreq:
	s3c24xx_i2c_deregister_cpufreq(i2c);

 err_irq:
	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);

	s3c24xx_i2c_deregister_cpufreq(i2c);

	i2c_del_adapter(&i2c->adap);
	free_irq(i2c->irq->start, i2c);