1/* 2 * Copyright 2008 Analog Devices Inc. 3 * 4 * Licensed under the GPL-2 or later. 5 */ 6 7#include <linux/cdev.h> 8#include <linux/device.h> 9#include <linux/errno.h> 10#include <linux/fs.h> 11#include <linux/kernel.h> 12#include <linux/module.h> 13#include <linux/platform_device.h> 14#include <linux/types.h> 15#include <linux/cpufreq.h> 16 17#include <asm/delay.h> 18#include <asm/dpmc.h> 19 20#define DRIVER_NAME "bfin dpmc" 21 22struct bfin_dpmc_platform_data *pdata; 23 24/** 25 * bfin_set_vlev - Update VLEV field in VR_CTL Reg. 26 * Avoid BYPASS sequence 27 */ 28static void bfin_set_vlev(unsigned int vlev) 29{ 30 unsigned pll_lcnt; 31 32 pll_lcnt = bfin_read_PLL_LOCKCNT(); 33 34 bfin_write_PLL_LOCKCNT(1); 35 bfin_write_VR_CTL((bfin_read_VR_CTL() & ~VLEV) | vlev); 36 bfin_write_PLL_LOCKCNT(pll_lcnt); 37} 38 39/** 40 * bfin_get_vlev - Get CPU specific VLEV from platform device data 41 */ 42static unsigned int bfin_get_vlev(unsigned int freq) 43{ 44 int i; 45 46 if (!pdata) 47 goto err_out; 48 49 freq >>= 16; 50 51 for (i = 0; i < pdata->tabsize; i++) 52 if (freq <= (pdata->tuple_tab[i] & 0xFFFF)) 53 return pdata->tuple_tab[i] >> 16; 54 55err_out: 56 printk(KERN_WARNING "DPMC: No suitable CCLK VDDINT voltage pair found\n"); 57 return VLEV_120; 58} 59 60#ifdef CONFIG_CPU_FREQ 61# ifdef CONFIG_SMP 62static void bfin_idle_this_cpu(void *info) 63{ 64 unsigned long flags = 0; 65 unsigned long iwr0, iwr1, iwr2; 66 unsigned int cpu = smp_processor_id(); 67 68 local_irq_save_hw(flags); 69 bfin_iwr_set_sup0(&iwr0, &iwr1, &iwr2); 70 71 platform_clear_ipi(cpu, IRQ_SUPPLE_0); 72 SSYNC(); 73 asm("IDLE;"); 74 bfin_iwr_restore(iwr0, iwr1, iwr2); 75 76 local_irq_restore_hw(flags); 77} 78 79static void bfin_idle_cpu(void) 80{ 81 smp_call_function(bfin_idle_this_cpu, NULL, 0); 82} 83 84static void bfin_wakeup_cpu(void) 85{ 86 unsigned int cpu; 87 unsigned int this_cpu = smp_processor_id(); 88 cpumask_t mask; 89 90 cpumask_copy(&mask, cpu_online_mask); 91 cpumask_clear_cpu(this_cpu, &mask); 92 for_each_cpu(cpu, &mask) 93 platform_send_ipi_cpu(cpu, IRQ_SUPPLE_0); 94} 95 96# else 97static void bfin_idle_cpu(void) {} 98static void bfin_wakeup_cpu(void) {} 99# endif 100 101static int 102vreg_cpufreq_notifier(struct notifier_block *nb, unsigned long val, void *data) 103{ 104 struct cpufreq_freqs *freq = data; 105 106 if (freq->cpu != CPUFREQ_CPU) 107 return 0; 108 109 if (val == CPUFREQ_PRECHANGE && freq->old < freq->new) { 110 bfin_idle_cpu(); 111 bfin_set_vlev(bfin_get_vlev(freq->new)); 112 udelay(pdata->vr_settling_time); /* Wait until Volatge settled */ 113 bfin_wakeup_cpu(); 114 } else if (val == CPUFREQ_POSTCHANGE && freq->old > freq->new) { 115 bfin_idle_cpu(); 116 bfin_set_vlev(bfin_get_vlev(freq->new)); 117 bfin_wakeup_cpu(); 118 } 119 120 return 0; 121} 122 123static struct notifier_block vreg_cpufreq_notifier_block = { 124 .notifier_call = vreg_cpufreq_notifier 125}; 126#endif /* CONFIG_CPU_FREQ */ 127 128/** 129 * bfin_dpmc_probe - 130 * 131 */ 132static int __devinit bfin_dpmc_probe(struct platform_device *pdev) 133{ 134 if (pdev->dev.platform_data) 135 pdata = pdev->dev.platform_data; 136 else 137 return -EINVAL; 138 139 return cpufreq_register_notifier(&vreg_cpufreq_notifier_block, 140 CPUFREQ_TRANSITION_NOTIFIER); 141} 142 143/** 144 * bfin_dpmc_remove - 145 */ 146static int __devexit bfin_dpmc_remove(struct platform_device *pdev) 147{ 148 pdata = NULL; 149 return cpufreq_unregister_notifier(&vreg_cpufreq_notifier_block, 150 CPUFREQ_TRANSITION_NOTIFIER); 151} 152 153struct platform_driver bfin_dpmc_device_driver = { 154 .probe = bfin_dpmc_probe, 155 .remove = __devexit_p(bfin_dpmc_remove), 156 .driver = { 157 .name = DRIVER_NAME, 158 } 159}; 160 161/** 162 * bfin_dpmc_init - Init driver 163 */ 164static int __init bfin_dpmc_init(void) 165{ 166 return platform_driver_register(&bfin_dpmc_device_driver); 167} 168module_init(bfin_dpmc_init); 169 170/** 171 * bfin_dpmc_exit - break down driver 172 */ 173static void __exit bfin_dpmc_exit(void) 174{ 175 platform_driver_unregister(&bfin_dpmc_device_driver); 176} 177module_exit(bfin_dpmc_exit); 178 179MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); 180MODULE_DESCRIPTION("cpu power management driver for Blackfin"); 181MODULE_LICENSE("GPL"); 182