[PATCH 8/9] Experimental S3C2410A cpufreq driver (fb)

Cesar Eduardo Barros cesarb at cesarb.net
Wed Feb 13 02:24:36 CET 2008


This is the cpufreq notifier for the S3C2410 framebuffer driver.

Signed-off-by: Cesar Eduardo Barros <cesarb at cesarb.net>
---
 drivers/video/Kconfig     |    2 +-
 drivers/video/s3c2410fb.c |  174 ++++++++++++++++++++++++++++++++++++++++++++-
 drivers/video/s3c2410fb.h |    7 ++
 3 files changed, 179 insertions(+), 4 deletions(-)

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 5b3dbcf..94ba455 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -1784,7 +1784,7 @@ config FB_W100
 
 config FB_S3C2410
 	tristate "S3C2410 LCD framebuffer support"
-	depends on FB && ARCH_S3C2410
+	depends on FB && ARCH_S3C2410 && (CPU_FREQ=n || CPU_FREQ_S3C2410)
 	select FB_CFB_FILLRECT
 	select FB_CFB_COPYAREA
 	select FB_CFB_IMAGEBLIT
diff --git a/drivers/video/s3c2410fb.c b/drivers/video/s3c2410fb.c
index b3c31d9..f63a68b 100644
--- a/drivers/video/s3c2410fb.c
+++ b/drivers/video/s3c2410fb.c
@@ -84,6 +84,8 @@
 #include <linux/interrupt.h>
 #include <linux/platform_device.h>
 #include <linux/clk.h>
+#include <linux/cpufreq.h>
+#include <linux/console.h>
 
 #include <asm/io.h>
 #include <asm/div64.h>
@@ -92,6 +94,7 @@
 #include <asm/arch/regs-lcd.h>
 #include <asm/arch/regs-gpio.h>
 #include <asm/arch/fb.h>
+#include <asm/arch/s3c2410-cpufreq.h>
 
 #ifdef CONFIG_PM
 #include <linux/pm.h>
@@ -141,10 +144,10 @@ static void s3c2410fb_set_lcdaddr(struct fb_info *info)
  *
  * calculate divisor for clk->pixclk
  */
-static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi,
-					  unsigned long pixclk)
+static unsigned int s3c2410fb_calc_pixclk_clk(struct s3c2410fb_info *fbi,
+					      unsigned long pixclk,
+					      unsigned long clk)
 {
-	unsigned long clk = clk_get_rate(fbi->clk);
 	unsigned long long div;
 
 	/* pixclk is in picoseconds, our clock is in Hz
@@ -160,6 +163,13 @@ static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi,
 	return div;
 }
 
+static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi,
+					  unsigned long pixclk)
+{
+	unsigned long clk = clk_get_rate(fbi->clk);
+	return s3c2410fb_calc_pixclk_clk(fbi, pixclk, clk);
+}
+
 /*
  *	s3c2410fb_check_var():
  *	Get the video params out of 'var'. If a value doesn't fit, round it up,
@@ -463,6 +473,8 @@ static void s3c2410fb_activate_var(struct fb_info *info)
 
 	fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
 	writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
+
+	s3c2410_cpufreq_schedule_update_policy();
 }
 
 /*
@@ -777,6 +789,139 @@ static irqreturn_t s3c2410fb_irq(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
+#ifdef CONFIG_CPU_FREQ
+
+#define S3C2410_LCDCON1_CLKVAL_MASK S3C2410_LCDCON1_CLKVAL((1<<10) - 1)
+
+static void s3c2410fb_cpufreq_update_clkval(struct fb_info *info,
+		unsigned long clk)
+{
+	/* Based on s3c2410fb_activate_var. */
+	struct s3c2410fb_info *fbi = info->par;
+	void __iomem *regs = fbi->io;
+	int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT;
+	struct fb_var_screeninfo *var = &info->var;
+	int clkdiv = s3c2410fb_calc_pixclk_clk(fbi, var->pixclock, clk) / 2;
+
+	if (type == S3C2410_LCDCON1_TFT) {
+		--clkdiv;
+		if (clkdiv < 0)
+			clkdiv = 0;
+	} else {
+		if (clkdiv < 2)
+			clkdiv = 2;
+	}
+
+	fbi->regs.lcdcon1 &=  ~S3C2410_LCDCON1_CLKVAL_MASK;
+	fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);
+
+	writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
+}
+
+static bool s3c2410fb_cpufreq_validate_clkval(struct fb_info *info,
+		unsigned long clk)
+{
+	/* Based on s3c2410fb_activate_var. */
+	struct s3c2410fb_info *fbi = info->par;
+	int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT;
+	struct fb_var_screeninfo *var = &info->var;
+	int clkdiv = s3c2410fb_calc_pixclk_clk(fbi, var->pixclock, clk) / 2;
+
+	if (type == S3C2410_LCDCON1_TFT) {
+		--clkdiv;
+		if (clkdiv < 0)
+			return false;
+	} else {
+		if (clkdiv < 2)
+			return false;
+	}
+
+	return true;
+}
+
+static int s3c2410fb_cpufreq_transition_notifier(struct notifier_block *nb,
+		unsigned long val, void *data)
+{
+	struct s3c2410fb_info *fbi =
+		container_of(nb, struct s3c2410fb_info, cpufreq_transition_nb);
+	struct fb_info *info = dev_get_drvdata(fbi->dev);
+	struct cpufreq_freqs *freqs = data;
+	const struct s3c2410_cpufreq_freqs *target_freqs =
+		s3c2410_cpufreq_target_freqs(freqs);
+	unsigned long clk;
+
+	/*
+	 * The comment for fb_ops implies the correct locking here
+	 * is console_sem.
+	 */
+
+	switch (val) {
+	case CPUFREQ_PRECHANGE:
+		if (target_freqs)
+			if (target_freqs->new.hclk > target_freqs->old.hclk) {
+				acquire_console_sem();
+				s3c2410fb_cpufreq_update_clkval(info,
+						target_freqs->new.hclk);
+				release_console_sem();
+			}
+		break;
+	case CPUFREQ_POSTCHANGE:
+		if (target_freqs) {
+			if (target_freqs->old.hclk > target_freqs->new.hclk) {
+				acquire_console_sem();
+				s3c2410fb_cpufreq_update_clkval(info,
+						target_freqs->new.hclk);
+				release_console_sem();
+			}
+			break;
+		}
+		/* fall through */
+	case CPUFREQ_RESUMECHANGE:
+	case CPUFREQ_SUSPENDCHANGE:
+		/* target_freqs is not valid */
+		clk = clk_get_rate(fbi->clk);
+		acquire_console_sem();
+		s3c2410fb_cpufreq_update_clkval(info, clk);
+		release_console_sem();
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static bool s3c2410fb_cpufreq_policy_adjust_cb(void *data,
+		const struct s3c2410_cpufreq_clocks *clocks)
+{
+	struct fb_info *info = data;
+
+	return s3c2410fb_cpufreq_validate_clkval(info, clocks->hclk);
+}
+
+static int s3c2410fb_cpufreq_policy_notifier(struct notifier_block *nb,
+		unsigned long val, void *data)
+{
+	struct s3c2410fb_info *fbi =
+		container_of(nb, struct s3c2410fb_info, cpufreq_policy_nb);
+	struct fb_info *info = dev_get_drvdata(fbi->dev);
+	struct cpufreq_policy *policy = data;
+
+	switch (val)
+	{
+	case CPUFREQ_ADJUST:
+		s3c2410_cpufreq_adjust_table(policy,
+				s3c2410fb_cpufreq_policy_adjust_cb, info);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+#endif
+
 static char driver_name[] = "s3c2410fb";
 
 static int __init s3c2410fb_probe(struct platform_device *pdev)
@@ -920,6 +1065,20 @@ static int __init s3c2410fb_probe(struct platform_device *pdev)
 		goto free_video_memory;
 	}
 
+#ifdef CONFIG_CPU_FREQ
+	info->cpufreq_transition_nb.notifier_call
+		= s3c2410fb_cpufreq_transition_notifier;
+	cpufreq_register_notifier(&info->cpufreq_transition_nb,
+		CPUFREQ_TRANSITION_NOTIFIER);
+
+	info->cpufreq_policy_nb.notifier_call
+		= s3c2410fb_cpufreq_policy_notifier;
+	cpufreq_register_notifier(&info->cpufreq_policy_nb,
+		CPUFREQ_POLICY_NOTIFIER);
+
+	s3c2410_cpufreq_update_policy();
+#endif
+
 	/* create device files */
 	device_create_file(&pdev->dev, &dev_attr_debug);
 
@@ -971,11 +1130,20 @@ static int s3c2410fb_remove(struct platform_device *pdev)
 	struct s3c2410fb_info *info = fbinfo->par;
 	int irq;
 
+#ifdef CONFIG_CPU_FREQ
+	cpufreq_unregister_notifier(&info->cpufreq_transition_nb,
+		CPUFREQ_TRANSITION_NOTIFIER);
+	cpufreq_unregister_notifier(&info->cpufreq_policy_nb,
+		CPUFREQ_POLICY_NOTIFIER);
+#endif
+
 	unregister_framebuffer(fbinfo);
 
 	s3c2410fb_stop_lcd(info);
 	msleep(1);
 
+	s3c2410_cpufreq_update_policy();
+
 	s3c2410fb_unmap_video_memory(fbinfo);
 
 	if (info->clk) {
diff --git a/drivers/video/s3c2410fb.h b/drivers/video/s3c2410fb.h
index 6ce5dc2..ade71cb 100644
--- a/drivers/video/s3c2410fb.h
+++ b/drivers/video/s3c2410fb.h
@@ -25,6 +25,8 @@
 #ifndef __S3C2410FB_H
 #define __S3C2410FB_H
 
+#include <linux/notifier.h>
+
 struct s3c2410fb_info {
 	struct device		*dev;
 	struct clk		*clk;
@@ -39,6 +41,11 @@ struct s3c2410fb_info {
 	/* keep these registers in case we need to re-write palette */
 	u32			palette_buffer[256];
 	u32			pseudo_pal[16];
+
+#ifdef CONFIG_CPU_FREQ
+	struct notifier_block	cpufreq_transition_nb;
+	struct notifier_block	cpufreq_policy_nb;
+#endif
 };
 
 #define PALETTE_BUFF_CLEAR (0x80000000)	/* entry is clear/invalid */
-- 
1.5.4





More information about the openmoko-kernel mailing list