/* * Copyright (c) 2009, Code Aurora Forum. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include "avs.h" #define AVSDSCR_INPUT 0x01004860 /* magic # from circuit designer */ #define TSCSR_INPUT 0x00000001 /* enable temperature sense */ #define TEMPRS 16 /* total number of temperature regions */ #define GET_TEMPR() (avs_get_tscsr() >> 28) /* scale TSCSR[CTEMP] to regions */ struct mutex avs_lock; static struct avs_state_s { u32 freq_cnt; /* Frequencies supported list */ short *avs_v; /* Dyanmically allocated storage for * 2D table of voltages over temp & * freq. Used as a set of 1D tables. * Each table is for a single temp. * For usage see avs_get_voltage */ int (*set_vdd) (int); /* Function Ptr for setting voltage */ int changing; /* Clock frequency is changing */ u32 freq_idx; /* Current frequency index */ int vdd; /* Current ACPU voltage */ } avs_state; /* * Update the AVS voltage vs frequency table, for current temperature * Adjust based on the AVS delay circuit hardware status */ static void avs_update_voltage_table(short *vdd_table) { u32 avscsr; int cpu; int vu; int l2; int i; u32 cur_freq_idx; short cur_voltage; cur_freq_idx = avs_state.freq_idx; cur_voltage = avs_state.vdd; avscsr = avs_test_delays(); AVSDEBUG("avscsr=%x, avsdscr=%x\n", avscsr, avs_get_avsdscr()); /* * Read the results for the various unit's AVS delay circuits * 2=> up, 1=>down, 0=>no-change */ cpu = ((avscsr >> 23) & 2) + ((avscsr >> 16) & 1); vu = ((avscsr >> 28) & 2) + ((avscsr >> 21) & 1); l2 = ((avscsr >> 29) & 2) + ((avscsr >> 22) & 1); if ((cpu == 3) || (vu == 3) || (l2 == 3)) { printk(KERN_ERR "AVS: Dly Synth O/P error\n"); } else if ((cpu == 2) || (l2 == 2) || (vu == 2)) { /* * even if one oscillator asks for up, increase the voltage, * as its an indication we are running outside the * critical acceptable range of v-f combination. */ AVSDEBUG("cpu=%d l2=%d vu=%d\n", cpu, l2, vu); AVSDEBUG("Voltage up at %d\n", cur_freq_idx); if (cur_voltage >= VOLTAGE_MAX) printk(KERN_ERR "AVS: Voltage can not get high enough!\n"); /* Raise the voltage for all frequencies */ for (i = 0; i < avs_state.freq_cnt; i++) { vdd_table[i] = cur_voltage + VOLTAGE_STEP; if (vdd_table[i] > VOLTAGE_MAX) vdd_table[i] = VOLTAGE_MAX; } } else if ((cpu == 1) && (l2 == 1) && (vu == 1)) { if ((cur_voltage - VOLTAGE_STEP >= VOLTAGE_MIN) && (cur_voltage <= vdd_table[cur_freq_idx])) { vdd_table[cur_freq_idx] = cur_voltage - VOLTAGE_STEP; AVSDEBUG("Voltage down for %d and lower levels\n", cur_freq_idx); /* clamp to this voltage for all lower levels */ for (i = 0; i < cur_freq_idx; i++) { if (vdd_table[i] > vdd_table[cur_freq_idx]) vdd_table[i] = vdd_table[cur_freq_idx]; } } } } /* * Return the voltage for the target performance freq_idx and optionally * use AVS hardware to check the present voltage freq_idx */ static short avs_get_target_voltage(int freq_idx, bool update_table) { unsigned cur_tempr = GET_TEMPR(); unsigned temp_index = cur_tempr*avs_state.freq_cnt; /* Table of voltages vs frequencies for this temp */ short *vdd_table = avs_state.avs_v + temp_index; if (update_table) avs_update_voltage_table(vdd_table); return vdd_table[freq_idx]; } /* * Set the voltage for the freq_idx and optionally * use AVS hardware to update the voltage */ static int avs_set_target_voltage(int freq_idx, bool update_table) { int rc = 0; int new_voltage = avs_get_target_voltage(freq_idx, update_table); if (avs_state.vdd != new_voltage) { AVSDEBUG("AVS setting V to %d mV @%d\n", new_voltage, freq_idx); rc = avs_state.set_vdd(new_voltage); if (rc) return rc; avs_state.vdd = new_voltage; } return rc; } /* * Notify avs of clk frquency transition begin & end */ int avs_adjust_freq(u32 freq_idx, int begin) { int rc = 0; if (!avs_state.set_vdd) { /* AVS not initialized */ return 0; } if (freq_idx >= avs_state.freq_cnt) { AVSDEBUG("Out of range :%d\n", freq_idx); return -EINVAL; } mutex_lock(&avs_lock); if ((begin && (freq_idx > avs_state.freq_idx)) || (!begin && (freq_idx < avs_state.freq_idx))) { /* Update voltage before increasing frequency & * after decreasing frequency */ rc = avs_set_target_voltage(freq_idx, 0); if (rc) goto aaf_out; avs_state.freq_idx = freq_idx; } avs_state.changing = begin; aaf_out: mutex_unlock(&avs_lock); return rc; } static struct delayed_work avs_work; static struct workqueue_struct *kavs_wq; #define AVS_DELAY ((CONFIG_HZ * 50 + 999) / 1000) static void do_avs_timer(struct work_struct *work) { int cur_freq_idx; mutex_lock(&avs_lock); if (!avs_state.changing) { /* Only adjust the voltage if clk is stable */ cur_freq_idx = avs_state.freq_idx; avs_set_target_voltage(cur_freq_idx, 1); } mutex_unlock(&avs_lock); queue_delayed_work_on(0, kavs_wq, &avs_work, AVS_DELAY); } static void __init avs_timer_init(void) { INIT_DELAYED_WORK_DEFERRABLE(&avs_work, do_avs_timer); queue_delayed_work_on(0, kavs_wq, &avs_work, AVS_DELAY); } static void __exit avs_timer_exit(void) { cancel_delayed_work(&avs_work); } static int __init avs_work_init(void) { kavs_wq = create_workqueue("avs"); if (!kavs_wq) { printk(KERN_ERR "AVS initialization failed\n"); return -EFAULT; } avs_timer_init(); return 1; } static void __exit avs_work_exit(void) { avs_timer_exit(); destroy_workqueue(kavs_wq); } int __init avs_init(int (*set_vdd)(int), u32 freq_cnt, u32 freq_idx) { int i; mutex_init(&avs_lock); if (freq_cnt == 0) return -EINVAL; avs_state.freq_cnt = freq_cnt; if (freq_idx >= avs_state.freq_cnt) return -EINVAL; avs_state.avs_v = kmalloc(TEMPRS * avs_state.freq_cnt * sizeof(avs_state.avs_v[0]), GFP_KERNEL); if (avs_state.avs_v == 0) return -ENOMEM; for (i = 0; i < TEMPRS*avs_state.freq_cnt; i++) avs_state.avs_v[i] = VOLTAGE_MAX; avs_reset_delays(AVSDSCR_INPUT); avs_set_tscsr(TSCSR_INPUT); avs_state.set_vdd = set_vdd; avs_state.changing = 0; avs_state.freq_idx = -1; avs_state.vdd = -1; avs_adjust_freq(freq_idx, 0); avs_work_init(); return 0; } void __exit avs_exit() { avs_work_exit(); kfree(avs_state.avs_v); }