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 22#define dprintk(msg...) \ 23 cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER, DRIVER_NAME, msg) 24 25struct bfin_dpmc_platform_data *pdata; 26 27/** 28 * bfin_set_vlev - Update VLEV field in VR_CTL Reg. 29 * Avoid BYPASS sequence 30 */ 31static void bfin_set_vlev(unsigned int vlev) 32{ 33 unsigned pll_lcnt; 34 35 pll_lcnt = bfin_read_PLL_LOCKCNT(); 36 37 bfin_write_PLL_LOCKCNT(1); 38 bfin_write_VR_CTL((bfin_read_VR_CTL() & ~VLEV) | vlev); 39 bfin_write_PLL_LOCKCNT(pll_lcnt); 40} 41 42/** 43 * bfin_get_vlev - Get CPU specific VLEV from platform device data 44 */ 45static unsigned int bfin_get_vlev(unsigned int freq) 46{ 47 int i; 48 49 if (!pdata) 50 goto err_out; 51 52 freq >>= 16; 53 54 for (i = 0; i < pdata->tabsize; i++) 55 if (freq <= (pdata->tuple_tab[i] & 0xFFFF)) 56 return pdata->tuple_tab[i] >> 16; 57 58err_out: 59 printk(KERN_WARNING "DPMC: No suitable CCLK VDDINT voltage pair found\n"); 60 return VLEV_120; 61} 62 63#ifdef CONFIG_CPU_FREQ 64static int 65vreg_cpufreq_notifier(struct notifier_block *nb, unsigned long val, void *data) 66{ 67 struct cpufreq_freqs *freq = data; 68 69 if (val == CPUFREQ_PRECHANGE && freq->old < freq->new) { 70 bfin_set_vlev(bfin_get_vlev(freq->new)); 71 udelay(pdata->vr_settling_time); /* Wait until Volatge settled */ 72 73 } else if (val == CPUFREQ_POSTCHANGE && freq->old > freq->new) 74 bfin_set_vlev(bfin_get_vlev(freq->new)); 75 76 return 0; 77} 78 79static struct notifier_block vreg_cpufreq_notifier_block = { 80 .notifier_call = vreg_cpufreq_notifier 81}; 82#endif /* CONFIG_CPU_FREQ */ 83 84/** 85 * bfin_dpmc_probe - 86 * 87 */ 88static int __devinit bfin_dpmc_probe(struct platform_device *pdev) 89{ 90 if (pdev->dev.platform_data) 91 pdata = pdev->dev.platform_data; 92 else 93 return -EINVAL; 94 95 return cpufreq_register_notifier(&vreg_cpufreq_notifier_block, 96 CPUFREQ_TRANSITION_NOTIFIER); 97} 98 99/** 100 * bfin_dpmc_remove - 101 */ 102static int __devexit bfin_dpmc_remove(struct platform_device *pdev) 103{ 104 pdata = NULL; 105 return cpufreq_unregister_notifier(&vreg_cpufreq_notifier_block, 106 CPUFREQ_TRANSITION_NOTIFIER); 107} 108 109struct platform_driver bfin_dpmc_device_driver = { 110 .probe = bfin_dpmc_probe, 111 .remove = __devexit_p(bfin_dpmc_remove), 112 .driver = { 113 .name = DRIVER_NAME, 114 } 115}; 116 117/** 118 * bfin_dpmc_init - Init driver 119 */ 120static int __init bfin_dpmc_init(void) 121{ 122 return platform_driver_register(&bfin_dpmc_device_driver); 123} 124module_init(bfin_dpmc_init); 125 126/** 127 * bfin_dpmc_exit - break down driver 128 */ 129static void __exit bfin_dpmc_exit(void) 130{ 131 platform_driver_unregister(&bfin_dpmc_device_driver); 132} 133module_exit(bfin_dpmc_exit); 134 135MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); 136MODULE_DESCRIPTION("cpu power management driver for Blackfin"); 137MODULE_LICENSE("GPL"); 138