[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