/************************************************************************** * linux/arch/arm/mach-dm320/cm.c * * DM320 Clock Management * * Copyright (c) 2006 Kent Ryhorchuk * **************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int idle_service_level[CM_N_CLOCKS] = {0, 0, 0, 0}; static int running_service_level[CM_N_CLOCKS] = {0xf, 0xf, 0, 0}; /* Defaults for the /proc interface. These ensure the board will run. */ static int proc_idle_service_level[CM_N_CLOCKS] = {0, 0, 0, 0}; static int proc_running_service_level[CM_N_CLOCKS] = {0, 0, 0, 0}; /* Dividers initialized in init. */ static uint16_t idle_dividers[CM_N_CLOCKS]; static uint16_t running_dividers[CM_N_CLOCKS]; static uint16_t idle_refctl = 0x140; static uint16_t running_refctl = 0x140; static uint16_t min_cpu_divider = 0; /* This is the base value for the SDRAM refresh cycle. You divide this by the SDRAM clock divider and add 1 to get the SDRAM refresh cycle base. */ #define REF_CYC_BASE 190 // 64MB, 8K cycle, 64ms. #define REF_CYC_FOR_DIV(D) ( REF_CYC_BASE / D ) // 0,1 invalid (illegal freqs) /* Returns the desired setting of DIV0, including AHB. */ static uint16_t cpu_divider_for_sl(int sl) { uint16_t ret; if( sl > 0xf ) sl = 0xf; /* ARM clock divider is 5 bits, service levels are 4. So shift by 1. */ /* Add 1 so the minimum divider is 2. TODO - determine the minimum level some other way. */ ret = ( ( 0xf - sl ) << 1 ) + 1; if( ret < min_cpu_divider ) ret = min_cpu_divider; /* If the ARM divider is 2 or less, set the AHB divider to 2. */ if( ret <= 1 ) ret |= 0x0100; return ret; } /* Determined in init. */ static uint16_t min_ram_divider = 0; static uint16_t ram_divider_for_sl(int sl) { uint16_t ret; if( sl > 0xf ) sl = 0xf; ret = ( 0xf - sl ) << 1; /* Allow for 0. */ /* On some boards the minimum divider is 1, on others it is 3. We use what was found at startup as the minimum. */ if( ret < min_ram_divider ) ret = min_ram_divider; return ret; } /* Determined in init. */ static uint16_t min_axl_divider = 0; static uint16_t axl_divider_for_sl(int sl, uint16_t ram_div) { uint16_t ret; /* DM320 Errata #10 - AXL clock has to be some amount faster than SDRAM. The inequality is 1/AXL + 0.0028 < 1/SDR . If you do the math for the PLLA only case, this means that AXL divider must be at least one less than the SDRAM divider. The inequality also holds for the case where SDRAM is clocked from PLLB and PLLB is running slower than PLLA. */ if( sl > 0xf ) sl = 0xf; ret = ( ( 0xf - sl ) << 1 ) + 1; /* Complicated by the fact that on some boards the RAM divider is 1. */ if( ram_div == 0 ){ ret = min_axl_divider; }else if( ret >= ram_div ){ ret = ram_div - 1; } if( ret < min_axl_divider ) ret = min_axl_divider; return ret; } static uint16_t min_dsp_divider = 0; static uint16_t dsp_divider_for_sl(int sl, uint16_t cpu_div) { uint16_t ret; if( sl > 0xf ) sl = 0xf; ret = ( ( 0xf - sl ) << 1 ) + 1; if( ret < min_dsp_divider ) ret = min_dsp_divider; if( cpu_div & 0x0100 ){ cpu_div &= 0xff; cpu_div += 1; } if( cpu_div < ret ) ret = cpu_div; return ret; } static void set_idle_divider(cm_clock_t clock) { switch( clock ){ case CM_CPU : idle_dividers[CM_CPU] = cpu_divider_for_sl(idle_service_level[CM_CPU]); break; case CM_RAM : case CM_AXL: idle_dividers[CM_RAM] = ram_divider_for_sl(idle_service_level[CM_RAM]); idle_dividers[CM_AXL] = axl_divider_for_sl(idle_service_level[CM_CPU], idle_dividers[CM_RAM]); idle_refctl = 0x100 | REF_CYC_FOR_DIV(idle_dividers[CM_RAM]); break; case CM_DSP : idle_dividers[CM_DSP] = dsp_divider_for_sl(idle_service_level[CM_DSP], idle_dividers[CM_CPU]); break; default: break; } } static void set_running_divider(cm_clock_t clock) { switch( clock ){ case CM_CPU : running_dividers[CM_CPU] = cpu_divider_for_sl(running_service_level[CM_CPU]); break; case CM_RAM : case CM_AXL : running_dividers[CM_RAM] = ram_divider_for_sl(running_service_level[CM_RAM]); running_dividers[CM_AXL] = axl_divider_for_sl(running_service_level[CM_CPU], running_dividers[CM_RAM]); running_refctl = 0x100 | REF_CYC_FOR_DIV(running_dividers[CM_RAM]); break; case CM_DSP : running_dividers[CM_DSP] = dsp_divider_for_sl(running_service_level[CM_DSP], running_dividers[CM_CPU]); break; default: break; } } static void set_dividers(void) { idle_dividers[CM_CPU] = cpu_divider_for_sl(idle_service_level[CM_CPU]); idle_dividers[CM_RAM] = ram_divider_for_sl(idle_service_level[CM_RAM]); idle_dividers[CM_AXL] = axl_divider_for_sl(idle_service_level[CM_CPU], idle_dividers[CM_RAM]); idle_dividers[CM_DSP] = dsp_divider_for_sl(idle_service_level[CM_DSP], idle_dividers[CM_CPU]); running_dividers[CM_CPU] = cpu_divider_for_sl(running_service_level[CM_CPU]); running_dividers[CM_RAM] = ram_divider_for_sl(running_service_level[CM_RAM]); running_dividers[CM_AXL] = axl_divider_for_sl(running_service_level[CM_CPU], running_dividers[CM_RAM]); running_dividers[CM_DSP] = dsp_divider_for_sl(running_service_level[CM_DSP], running_dividers[CM_CPU]); running_refctl = 0x100 | REF_CYC_FOR_DIV(running_dividers[CM_RAM]); idle_refctl = 0x100 | REF_CYC_FOR_DIV(idle_dividers[CM_RAM]); } int disable_throttle(cm_clock_t clock, int level) { unsigned long flags; local_irq_save(flags); idle_service_level[clock] += level; set_idle_divider(clock); local_irq_restore(flags); return level; } EXPORT_SYMBOL(disable_throttle); void enable_throttle(cm_clock_t clock, int token) { unsigned long flags; local_irq_save(flags); idle_service_level[clock] -= token; if( idle_service_level[clock] < 0 ) idle_service_level[clock] = 0; set_idle_divider(clock); local_irq_restore(flags); } EXPORT_SYMBOL(enable_throttle); int enable_boost(cm_clock_t clock, int level) { unsigned long flags; local_irq_save(flags); running_service_level[clock] += level; set_running_divider(clock); dm320_exit_idle(); local_irq_restore(flags); return level; } EXPORT_SYMBOL(enable_boost); void disable_boost(cm_clock_t clock, int token) { unsigned long flags; local_irq_save(flags); running_service_level[clock] -= token; if( idle_service_level[clock] < 0 ) idle_service_level[clock] = 0; set_running_divider(clock); dm320_exit_idle(); local_irq_restore(flags); } EXPORT_SYMBOL(disable_boost); void throttle_all(cm_setsl_in_t *a, cm_user_t *cmu) { int i; unsigned long flags; local_irq_save(flags); for( i = 0 ; i < CM_N_CLOCKS ; i++ ){ idle_service_level[i] -= cmu->idle_tokens[i]; idle_service_level[i] += cmu->idle_tokens[i] = a[0].sl[i]; running_service_level[i] -= cmu->running_tokens[i]; running_service_level[i] += cmu->running_tokens[i] = a[1].sl[i]; } set_dividers(); dm320_exit_idle(); local_irq_restore(flags); } void dm320_enter_idle(void) { uint16_t div0_new, div1_new, div2_new; div0_new = idle_dividers[CM_CPU]; /* Includes AHB. */ div1_new = ( idle_dividers[CM_AXL] << 8 ) | idle_dividers[CM_RAM]; div2_new = idle_dividers[CM_DSP] << 8; outw(div0_new, DM320_CLKC_DIV0); outw(div1_new, DM320_CLKC_DIV1); outw(div2_new, DM320_CLKC_DIV2); outw(idle_refctl, DM320_SDRAMC_REFCTL); } void dm320_exit_idle(void) { uint16_t div0_new, div1_new, div2_new; div0_new = running_dividers[CM_CPU]; div1_new = ( running_dividers[CM_AXL] << 8 ) | running_dividers[CM_RAM]; div2_new = running_dividers[CM_DSP] << 8; outw(div2_new, DM320_CLKC_DIV2); outw(div1_new, DM320_CLKC_DIV1); outw(div0_new, DM320_CLKC_DIV0); outw(running_refctl, DM320_SDRAMC_REFCTL); } /* This is the number of bits of precision for the loops_per_jiffy. Each bit takes on average 1.5/HZ seconds. This (like the original) is a little better than 1% */ #define LPS_PREC 8 void recalibrate_delay(void) { #if !defined(CONFIG_BOGOMIPS) || !CONFIG_BOGOMIPS unsigned long ticks, loopbit; int lps_precision = LPS_PREC; loops_per_jiffy = (1<<12); printk("Recalibrating delay loop... "); while (loops_per_jiffy <<= 1) { /* wait for "start of" clock tick */ ticks = jiffies; while (ticks == jiffies) /* nothing */; /* Go .. */ ticks = jiffies; __delay(loops_per_jiffy); ticks = jiffies - ticks; if (ticks) break; } /* Do a binary approximation to get loops_per_jiffy set to equal one clock (up to lps_precision bits) */ loops_per_jiffy >>= 1; loopbit = loops_per_jiffy; while ( lps_precision-- && (loopbit >>= 1) ) { loops_per_jiffy |= loopbit; ticks = jiffies; while (ticks == jiffies); ticks = jiffies; __delay(loops_per_jiffy); if (jiffies != ticks) /* longer than 1 tick */ loops_per_jiffy &= ~loopbit; } #else printk("Using pre-calculated value: "); #endif /* Round the value and print it */ printk("%lu.%02lu BogoMIPS\n", loops_per_jiffy/(500000/HZ), (loops_per_jiffy/(5000/HZ)) % 100); } static int throttle_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) { int len; len = sprintf(page, "\ idle: cpu %02d, ram %02d, axl %02d, dsp %02d\n\ idle_div: cpu %03x, ram %02x, axl %02x, dsp %02x\n\ running: cpu %02d, ram %02d, axl %02d, dsp %02d\n\ running_div: cpu %03x, ram %02x, axl %02x, dsp %02x\n\ ", idle_service_level[CM_CPU], idle_service_level[CM_RAM], idle_service_level[CM_AXL], idle_service_level[CM_DSP], idle_dividers[CM_CPU], idle_dividers[CM_RAM], idle_dividers[CM_AXL], idle_dividers[CM_DSP], running_service_level[CM_CPU], running_service_level[CM_RAM], running_service_level[CM_AXL], running_service_level[CM_DSP], running_dividers[CM_CPU], running_dividers[CM_RAM], running_dividers[CM_AXL], running_dividers[CM_DSP]); len -= off; *start = page + off; if (len > count) len = count; else *eof = 1; if (len < 0) len = 0; return len; } static cm_user_t proc_cmu; static int throttle_write_proc(struct file *file, const char __user *buffer, unsigned long count, void *data) { int len, i; unsigned long num; int sl_in[CM_N_CLOCKS]; char str[20]; len = sizeof(str); if (count < len) len = count; if (copy_from_user(str, buffer, len) > 0) return -EFAULT; num = sscanf(str, " %d %d %d %d ", &sl_in[0], &sl_in[1], &sl_in[2], &sl_in[3]); if( num != 4 ){ printk("Invalid string written to throttle.\n"); return -EINVAL; } for( i = 0 ; i < CM_N_CLOCKS ; i++ ){ enable_throttle(i, proc_cmu.idle_tokens[i]); proc_cmu.idle_tokens[i] = disable_throttle(i, sl_in[i]); } return count; } #define THROTTLE_CHAR_MAJOR 154 #define THROTTLE_CPU_MINOR 0 #define THROTTLE_RAM_MINOR 1 static cm_user_t *users[CM_MAX_USERS]; void throttle_add(int index) { devfs_mk_cdev(MKDEV(THROTTLE_CHAR_MAJOR, index), S_IFCHR | S_IRUGO | S_IWUGO, "throttle/%d", index); } void throttle_remove(int index) { devfs_remove("throttle/%d", index); } #define TO_CM_USER(file) (cm_user_t *)(file->private_data) static int throttle_open(struct inode *inode, struct file *file) { int i; cm_user_t *cmu; /* Check for space in the list */ for( i = 0 ; i < CM_MAX_USERS ; i++ ) if( users[i] == NULL ) break; if( i == CM_MAX_USERS ) return -EMFILE; cmu = users[i] = kmalloc(sizeof(cm_user_t), GFP_KERNEL); if( users[i] == NULL ) return -ENOMEM; cmu->index = i; for( i = 0 ; i < CM_N_CLOCKS ; i++ ){ cmu->idle_tokens[i] = 0; cmu->running_tokens[i] = 0; } file->private_data = cmu; return 0; } static int throttle_close(struct inode *inode, struct file *file) { cm_user_t *cmu = TO_CM_USER(file); int i; unsigned long flags; users[cmu->index] = NULL; /* Remove users influence on the dividers and reset them. */ local_irq_save(flags); for( i = 0 ; i < CM_N_CLOCKS ; i++ ){ enable_throttle(i, cmu->idle_tokens[i]); disable_boost(i, cmu->running_tokens[i]); } set_dividers(); dm320_exit_idle(); /* Apply running clocks now. */ local_irq_restore(flags); kfree(cmu); //printk("Close clock %d idle service level is now %d\n", clock, // idle_service_level[clock]); return 0; } static int throttle_ioctl(struct inode *inode, struct file *file, u_int cmd, u_long arg) { cm_user_t *cmu = TO_CM_USER(file); void __user *argp = (void __user *)arg; cm_setsl_in_t setsl_arg; cm_setsl_in_t setallsl_arg[2]; u_long size; int i; size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; if (cmd & IOC_IN) { if (!access_ok(VERIFY_READ, argp, size)) return -EFAULT; } if (cmd & IOC_OUT) { if (!access_ok(VERIFY_WRITE, argp, size)) return -EFAULT; } if( cmd == CM_SET_ALL_SL ){ if( copy_from_user(&setallsl_arg, argp, sizeof(setallsl_arg)) ) return -EFAULT; }else{ if( copy_from_user(&setsl_arg, argp, sizeof(setsl_arg)) ) return -EFAULT; } switch( cmd ){ case CM_SET_IDLE_SL : for( i = 0 ; i < CM_N_CLOCKS ; i++ ){ enable_throttle(i, cmu->idle_tokens[i]); cmu->idle_tokens[i] = disable_throttle(i, setsl_arg.sl[i]); } break; case CM_SET_RUNNING_SL : for( i = 0 ; i < CM_N_CLOCKS ; i++ ){ disable_boost(i, cmu->running_tokens[i]); cmu->running_tokens[i] = enable_boost(i, setsl_arg.sl[i]); } break; case CM_SET_ALL_SL : throttle_all(setallsl_arg, cmu); break; default: return -EINVAL; } return 0; } static ssize_t throttle_read(struct file *file, char __user *buf, size_t count,loff_t *ppos) { return -EINVAL; } static ssize_t throttle_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) { return -EINVAL; } static struct file_operations cm_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = throttle_read, .write = throttle_write, .open = throttle_open, .release = throttle_close, .ioctl = throttle_ioctl, }; static int __init cm_init(void) { struct proc_dir_entry *de; int i; printk("DM320 clock management\n"); if( register_chrdev(THROTTLE_CHAR_MAJOR, "throttle", &cm_fops) ){ printk("Can't allocate major number %d for throttle.\n", THROTTLE_CHAR_MAJOR); return -EAGAIN; } devfs_mk_dir("throttle"); throttle_add(0); de = create_proc_entry("throttle", 0644, NULL); if (!de) return -1; de->read_proc = (read_proc_t *) throttle_read_proc; de->write_proc = (write_proc_t *) throttle_write_proc; de->owner = THIS_MODULE; /* Minumum dividers. */ min_cpu_divider = inw(DM320_CLKC_DIV0) & 0x001f; min_ram_divider = inw(DM320_CLKC_DIV1) & 0x001f; min_axl_divider = ( inw(DM320_CLKC_DIV1) & 0x1f00 ) >> 8; min_dsp_divider = ( inw(DM320_CLKC_DIV2) & 0x1f00 ) >> 8; set_dividers(); for( i = 0 ; i < CM_N_CLOCKS ; i++ ){ proc_cmu.idle_tokens[i] = disable_throttle(i, proc_idle_service_level[i]); proc_cmu.running_tokens[i] = enable_boost(i, proc_running_service_level[i]); } return 0; } static void __exit cm_exit(void) { throttle_remove(0); unregister_chrdev(THROTTLE_CHAR_MAJOR, "throttle"); remove_proc_entry("throttle", NULL); } module_init(cm_init); module_exit(cm_exit);