blob: bdd0bba260d58db33ba66136328603a6b87684b7 [file] [log] [blame]
/*
* SmartReflex Voltage Control driver
*
* Copyright (C) 2011 Texas Instruments, Inc. - http://www.ti.com/
* Author: AnilKumar Ch <anilkumar@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <plat/ti81xx.h>
#include <plat/irqs-ti81xx.h>
#include <plat/smartreflex.h>
#include <plat/gpio.h>
#define GPIO_VAL_INIT (0xFF)
#define NUM_GPIO_BITS (4)
#define SENS_PER_VDOMAIN (2)
#define SEN_NAME_LEN (40)
#define MARGIN (0)
#define STEP_SIZE (80) /* (1/12.5mv)*/
#define MIN_VOLT (0x2F5)
#define MAX_VOLT (0x4AE)
#define THOU_MVOLT (1000)
#define gpio_num(bank_num, pin_num) (bank_num * 32 + pin_num)
#ifdef CONFIG_DEBUG_SR_CLASS2
static int sr_debugfs_enable = 1;
#else
static int sr_debugfs_enable;
#endif
struct tps40041_volt_data {
u8 gpio_val;
u32 mvolt;
};
static struct tps40041_volt_data volt_sd;
/*
* TODO: Populate this table based on silicon characterization data
*/
static struct tps40041_volt_data tps_volt_data[NUM_VOLT_LEVELS] = {
{0x0, 0x2F4}, {0x8, 0X32B}, {0x4, 0x330}, {0x2, 0x344},
{0xC, 0x367}, {0xA, 0x37B}, {0x6, 0x380}, {0xE, 0x3B7},
{0x1, 0x3EB}, {0x9, 0x422}, {0x5, 0x427}, {0x3, 0x43B},
{0xD, 0x45E}, {0xB, 0x472}, {0x7, 0x477}, {0xF, 0x4AE},
};
struct ti816x_sr_sensors {
const char *name;
struct clk *fck;
int irq_processed;
u32 nvalue;
s32 e2v_gain;
u32 base_addr;
};
struct ti816x_sr {
u8 init_gpio_val;
u8 curr_gpio_val;
u32 curr_volt;
int is_autocomp_active;
spinlock_t lock;
int nvalue_count;
struct ti816x_sr_sensors sen[SENS_PER_VDOMAIN];
struct timer_list timer;
int sr_intdelay;
};
static void sr_start_coreautocomp(struct ti816x_sr *sr);
static void sr_stop_coreautocomp(struct ti816x_sr *sr);
static struct ti816x_sr srcore = {
.sen[0] = {
.name = "hvt",
.irq_processed = 0,
.nvalue = 0,
.e2v_gain = (15),
.base_addr = TI816X_SR0_BASE,
},
.sen[1] = {
.name = "svt",
.irq_processed = 0,
.nvalue = 0,
.e2v_gain = (22),
.base_addr = TI816X_SR1_BASE,
},
.nvalue_count = 2,
.is_autocomp_active = 0,
.sr_intdelay = 2000, /* msec */
};
static inline void sr_write_reg(u32 srbase, int offset, u32 value)
{
omap_writel(value, srbase + offset);
}
static inline void sr_modify_reg(u32 srbase, int offset, u32 mask,
u32 value)
{
u32 reg_val;
reg_val = omap_readl(srbase + offset);
reg_val &= ~mask;
reg_val |= (value&mask);
omap_writel(reg_val, srbase + offset);
}
static inline u32 sr_read_reg(u32 srbase, int offset)
{
return omap_readl(srbase + offset);
}
static u8 sr_gpio_getn(void)
{
int i = 0;
int gpio_numb;
int gpio_val;
u8 cur_gpio_val = 0;
while (i < NUM_GPIO_BITS) {
gpio_numb = gpio_num(0, i);
gpio_request(gpio_numb, "srdrv");
gpio_val = gpio_get_value(gpio_numb);
cur_gpio_val = cur_gpio_val | (gpio_val << i);
i++;
}
srcore.curr_gpio_val = cur_gpio_val;
return cur_gpio_val;
}
static int sr_gpio_setn(u8 gpio_val)
{
int i = 0;
int gpio_numb;
while (i < NUM_GPIO_BITS) {
gpio_numb = gpio_num(0, i);
gpio_request(gpio_numb, "srdrv");
gpio_direction_output(gpio_numb, (gpio_val & (1 << i)));
i++;
}
return 0;
}
/*
* The routine reads the efuse register for getting current
* gpio voltage value
*/
static int get_core_voltage(void)
{
u8 gpio_val;
u32 mvolt = 0;
int loopcnt;
gpio_val = sr_gpio_getn();
/* Get the voltage from the table based on gpio_val */
for (loopcnt = 0; loopcnt < NUM_VOLT_LEVELS; loopcnt++) {
if (tps_volt_data[loopcnt].gpio_val == gpio_val) {
mvolt = tps_volt_data[loopcnt].mvolt;
break;
}
}
srcore.curr_volt = mvolt;
return mvolt;
}
static void get_near_gpio_val(s32 curr_volt)
{
int loopcnt, ncnt = 0;
s32 temp[NUM_VOLT_LEVELS], volt_greater[NUM_VOLT_LEVELS], swap_var;
volt_sd.gpio_val = GPIO_VAL_INIT;
/* Nearest GPIO val, nothing but picking the minimum diff
* gpio values from a group of values
*/
for (loopcnt = 0; loopcnt < NUM_VOLT_LEVELS; loopcnt++) {
temp[loopcnt] = tps_volt_data[loopcnt].mvolt - curr_volt;
if (temp[loopcnt] >= 0) {
volt_greater[ncnt] = temp[loopcnt];
ncnt++;
}
if (ncnt > 1) {
if (volt_greater[ncnt-2] < volt_greater[ncnt-1]) {
swap_var = volt_greater[ncnt-2];
volt_greater[ncnt-2] = volt_greater[ncnt-1];
volt_greater[ncnt-1] = swap_var;
}
}
}
for (loopcnt = 0; loopcnt < NUM_VOLT_LEVELS; loopcnt++) {
if (temp[loopcnt] == volt_greater[ncnt - 1]) {
volt_sd.mvolt = tps_volt_data[loopcnt].mvolt;
volt_sd.gpio_val = tps_volt_data[loopcnt].gpio_val;
break;
}
}
}
/*
* The routine get the error value and adjust the TPS40041 core
* voltage based on the decision from two sensors
* Assumptions:
* 1. The efuse data is programmed into HVT and SVT control regs
* 2. The efuse and gpio clocks are already enabled
*/
static int set_core_voltage(s32 sr1error, s32 sr2error)
{
int ret;
int current_volt;
int prev_volt;
unsigned long flags;
/* Get the current voltage from GPIO */
prev_volt = get_core_voltage();
if ((srcore.sen[0].irq_processed == 0) ||
(srcore.sen[1].irq_processed == 0))
return (volt_sd.mvolt == prev_volt);
if (sr1error > sr2error)
current_volt = prev_volt + sr1error;
else
current_volt = prev_volt + sr2error;
get_near_gpio_val(current_volt);
if (volt_sd.gpio_val == GPIO_VAL_INIT)
printk(KERN_ERR "Failed to get the gpio val = %d\n",
volt_sd.gpio_val);
spin_lock_irqsave(&srcore.lock, flags);
ret = sr_gpio_setn(volt_sd.gpio_val);
spin_unlock_irqrestore(&srcore.lock, flags);
if (ret)
printk(KERN_ERR "Failed to set the core voltage with GPIO\n");
srcore.sen[0].irq_processed = 0;
srcore.sen[1].irq_processed = 0;
return (volt_sd.mvolt == prev_volt);
}
static int get_errvolt(s32 srid)
{
u32 srbase;
s32 e2vgain;
s8 terror;
u32 senerror_reg;
s32 error, delta;
s32 steps, mvoltage;
if (srid == SRHVT) {
srbase = srcore.sen[0].base_addr;
e2vgain = srcore.sen[0].e2v_gain;
} else {
srbase = srcore.sen[1].base_addr;
e2vgain = srcore.sen[1].e2v_gain;
}
senerror_reg = sr_read_reg(srbase, SENERROR_V2);
senerror_reg = (senerror_reg & 0x0000FF00);
senerror_reg = senerror_reg >> 8;
terror = senerror_reg & 0x000000FF;
/* convert from binary to % error x 1000mv */
error = terror * 25 * THOU_MVOLT;
delta = ((error + MARGIN) * e2vgain) >> 5;
/* compute the (steps * 1000mv) */
steps = delta/STEP_SIZE;
/* Steps to volatge correction based on step size in mV*/
mvoltage = steps/STEP_SIZE;
return mvoltage;
}
static void irq_sr_timer(unsigned long data)
{
/* Enable both the interrupts */
sr_modify_reg(srcore.sen[0].base_addr, IRQENABLE_SET,
IRQENABLE_MCUBOUNDSINT,
IRQENABLE_MCUBOUNDSINT);
sr_modify_reg(srcore.sen[1].base_addr, IRQENABLE_SET,
IRQENABLE_MCUBOUNDSINT,
IRQENABLE_MCUBOUNDSINT);
}
static irqreturn_t srcore_class2_irq(int irq, void *dev_id)
{
int sr_irqdis;
s32 srhvt_delta;
s32 srsvt_delta;
srhvt_delta = get_errvolt(SRHVT);
srsvt_delta = get_errvolt(SRSVT);
if (irq == TI81XX_IRQ_SMRFLX0)
srcore.sen[0].irq_processed = 1;
else
srcore.sen[1].irq_processed = 1;
sr_irqdis = set_core_voltage(srhvt_delta, srsvt_delta);
if (sr_irqdis == 1) {
if (!timer_pending(&srcore.timer)) {
srcore.timer.data = irq;
srcore.timer.function = irq_sr_timer;
srcore.timer.expires = jiffies +
msecs_to_jiffies(srcore.sr_intdelay);
add_timer(&srcore.timer);
}
sr_modify_reg(srcore.sen[0].base_addr, IRQSTATUS,
IRQSTATUS_MCBOUNDSINT,
IRQSTATUS_MCBOUNDSINT);
sr_modify_reg(srcore.sen[1].base_addr, IRQSTATUS,
IRQSTATUS_MCBOUNDSINT,
IRQSTATUS_MCBOUNDSINT);
/* Disable the interrupt */
sr_modify_reg(srcore.sen[0].base_addr,
IRQENABLE_CLR,
IRQENABLE_MCUBOUNDSINT,
IRQENABLE_MCUBOUNDSINT);
sr_modify_reg(srcore.sen[1].base_addr,
IRQENABLE_CLR,
IRQENABLE_MCUBOUNDSINT,
IRQENABLE_MCUBOUNDSINT);
} else {
if (irq == TI81XX_IRQ_SMRFLX0) {
/* Clear MCUDisableAck Interrupt */
sr_modify_reg(srcore.sen[0].base_addr, IRQSTATUS,
IRQSTATUS_MCBOUNDSINT,
IRQSTATUS_MCBOUNDSINT);
} else {
/* Clear MCUDisableAck Interrupt */
sr_modify_reg(srcore.sen[1].base_addr, IRQSTATUS,
IRQSTATUS_MCBOUNDSINT,
IRQSTATUS_MCBOUNDSINT);
}
}
return IRQ_HANDLED;
}
static int sr_clk_enable(struct clk *fck)
{
if (clk_enable(fck) != 0) {
printk(KERN_ERR "Could not enable sr_fck\n");
return -1;
}
return 0;
}
static int sr_clk_disable(struct clk *fck)
{
clk_disable(fck);
return 0;
}
static int sr_set_nvalues(struct ti816x_sr *sr)
{
/* Read HVT values for CORE from EFUSE */
sr->sen[0].nvalue = omap_readl(CONTROL_FUSE_SMRT_HVT) & 0xFFFFFF;
/* Read SVT values for CORE from EFUSE */
sr->sen[1].nvalue = omap_readl(CONTROL_FUSE_SMRT_SVT) & 0xFFFFFF;
if ((sr->sen[0].nvalue == 0) || (sr->sen[1].nvalue == 0)) {
printk(KERN_ERR "SmartReflex Driver: Un-Characterized"
" silicon found\n");
return -1;
}
printk("SR NTarget value for HVT 0x%x\n", sr->sen[0].nvalue);
printk("SR NTarget value for SVT 0x%x\n", sr->sen[1].nvalue);
return 0;
}
static void sr_configure(u32 srbase, s32 srid)
{
u32 sr_config;
sr_config = SRCONFIG_ACCUM_DATA |
SRCLKLENGTH_27MHZ_SYSCLK |
SRCONFIG_SENENABLE | SRCONFIG_ERRGEN_EN |
SRCONFIG_MINMAXAVG_EN |
SRCONFIG_SENNENABLE |
SRCONFIG_SENPENABLE;
sr_write_reg(srbase, SRCONFIG, sr_config);
sr_write_reg(srbase, AVGWEIGHT, AVGWEIGHT_SENPAVGWEIGHT_MASK |
AVGWEIGHT_SENNAVGWEIGHT_MASK);
if (srid == SRHVT)
sr_modify_reg(srbase, ERRCONFIG_V2, (SR_ERRWEIGHT_MASK |
SR_ERRMAXLIMIT_MASK | SR_ERRMINLIMIT_MASK),
ERRCONFIG_ERRWEIGHT | ERRCONFIG_ERRMAXLIMIT |
ERRCONFIG_HVT_ERRMINLIMIT);
else
sr_modify_reg(srbase, ERRCONFIG_V2, (SR_ERRWEIGHT_MASK |
SR_ERRMAXLIMIT_MASK | SR_ERRMINLIMIT_MASK),
ERRCONFIG_ERRWEIGHT | ERRCONFIG_ERRMAXLIMIT |
ERRCONFIG_SVT_ERRMINLIMIT);
}
static void sr_enable(u32 srbase, u32 nvalue)
{
/* Enable MCUDisableAck Interrupt */
sr_modify_reg(srbase, IRQSTATUS_RAW,
IRQSTATUSRAW_MCUDISABLEACKINT,
IRQSTATUSRAW_MCUDISABLEACKINT);
/* SRCONFIG - disable SR */
sr_modify_reg(srbase, SRCONFIG, SRCONFIG_SRENABLE,
~SRCONFIG_SRENABLE);
if (nvalue == 0)
printk(KERN_ERR "OPP doesn't support SmartReflex\n");
sr_write_reg(srbase, NVALUERECIPROCAL, nvalue);
/* Clear MCUDisableAck Interrupt */
sr_modify_reg(srbase, IRQSTATUS,
IRQSTATUS_MCUDISABLEACKINT,
IRQSTATUS_MCUDISABLEACKINT);
/* Disable MCUDisableAck interrupt */
sr_modify_reg(srbase, IRQENABLE_CLR,
IRQSTATUS_MCUDISABLEACKINT,
IRQSTATUS_MCUDISABLEACKINT);
/* Enable the interrupt */
sr_modify_reg(srbase, IRQENABLE_SET,
IRQENABLE_MCUBOUNDSINT,
IRQENABLE_MCUBOUNDSINT);
/* SRCONFIG - enable SR */
sr_modify_reg(srbase, SRCONFIG, SRCONFIG_SRENABLE,
SRCONFIG_SRENABLE);
}
static void sr_disable(u32 srbase)
{
/* SRCONFIG - disable SR */
sr_modify_reg(srbase, SRCONFIG, SRCONFIG_SRENABLE,
~SRCONFIG_SRENABLE);
/* Disable the interrupt */
sr_modify_reg(srbase, IRQENABLE_CLR,
IRQENABLE_MCUBOUNDSINT,
IRQENABLE_MCUBOUNDSINT);
}
static void sr_start_coreautocomp(struct ti816x_sr *sr)
{
if ((sr->sen[0].nvalue == 0) || (sr->sen[1].nvalue == 0)) {
printk(KERN_ERR "SmartReflex Driver: Un-Characterized"
" silicon found\n");
return;
}
if (sr->is_autocomp_active == 1) {
printk(KERN_WARNING "SR VDD autocomp is already active\n");
return;
}
sr_clk_enable(sr->sen[0].fck);
sr_clk_enable(sr->sen[1].fck);
sr_configure(sr->sen[0].base_addr, SRHVT);
sr_configure(sr->sen[1].base_addr, SRSVT);
sr_enable(sr->sen[0].base_addr, sr->sen[0].nvalue);
sr_enable(sr->sen[1].base_addr, sr->sen[1].nvalue);
sr->is_autocomp_active = 1;
init_timer(&sr->timer);
}
static void sr_stop_coreautocomp(struct ti816x_sr *sr)
{
unsigned long flags;
int ret;
if (sr->is_autocomp_active == 0) {
printk(KERN_WARNING "SR VDD autocomp is not active\n");
return;
}
del_timer(&sr->timer);
sr->is_autocomp_active = 0;
sr_disable(sr->sen[0].base_addr);
sr_disable(sr->sen[1].base_addr);
sr_clk_disable(sr->sen[0].fck);
sr_clk_disable(sr->sen[1].fck);
spin_lock_irqsave(&sr->lock, flags);
ret = sr_gpio_setn(sr->init_gpio_val);
spin_unlock_irqrestore(&sr->lock, flags);
if (ret)
printk(KERN_ERR "Failed to set the initial voltage\n");
sr->curr_gpio_val = sr->init_gpio_val;
sr->curr_volt = get_core_voltage();
}
/* Sysfs interface to select SR CORE auto compensation */
static ssize_t sr_core_autocomp_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", srcore.is_autocomp_active);
}
static ssize_t sr_core_autocomp_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
unsigned short value;
if (sscanf(buf, "%hu", &value) != 1 || (value > 1)) {
printk(KERN_ERR "sr_core_autocomp: Invalid value\n");
return -EINVAL;
}
if (value == 0)
sr_stop_coreautocomp(&srcore);
else
sr_start_coreautocomp(&srcore);
return n;
}
static struct kobj_attribute sr_core_autocomp = {
.attr = {
.name = __stringify(sr_core_autocomp),
.mode = 0644,
},
.show = sr_core_autocomp_show,
.store = sr_core_autocomp_store,
};
static int sr_debugfs_entires(struct ti816x_sr *sr_info)
{
struct dentry *dbg_dir, *sen_dir;
int i;
dbg_dir = debugfs_create_dir("smartreflex", NULL);
if (IS_ERR(dbg_dir)) {
printk(KERN_ERR "Unable to create debugfs directory\n");
return PTR_ERR(dbg_dir);
}
(void) debugfs_create_x32("sr_interrupt_delay", S_IRUGO | S_IWUGO,
dbg_dir, &sr_info->sr_intdelay);
(void) debugfs_create_u32("sr_current_voltage", S_IRUGO, dbg_dir,
&sr_info->curr_volt);
(void) debugfs_create_u8("sr_init_gpio_val", S_IRUGO, dbg_dir,
&sr_info->init_gpio_val);
(void) debugfs_create_u8("sr_curr_gpio_val", S_IRUGO, dbg_dir,
&sr_info->curr_gpio_val);
for (i = 0; i < sr_info->nvalue_count; i++) {
char *name;
name = kzalloc(SEN_NAME_LEN + 1, GFP_KERNEL);
if (!name) {
printk(KERN_ERR "Unable to allocate memory for n-value"
"directory name\n");
return -ENOMEM;
}
sprintf(name, "%s", sr_info->sen[i].name);
sen_dir = debugfs_create_dir(name, dbg_dir);
if (IS_ERR(sen_dir)) {
printk(KERN_ERR "Unable to create debugfs directory\n");
return PTR_ERR(sen_dir);
}
(void) debugfs_create_x32("sr_err2voltgain", S_IRUGO, sen_dir,
&sr_info->sen[i].e2v_gain);
(void) debugfs_create_x32("sr_nvalue", S_IRUGO | S_IWUGO,
sen_dir, &sr_info->sen[i].nvalue);
}
return 0;
}
/* SR driver init */
static int __init sr_class2_init(void)
{
int ret = 0;
srcore.sen[0].fck = clk_get(NULL, "smartreflex_corehvt_fck");
if (IS_ERR(srcore.sen[0].fck)) {
printk(KERN_ERR "Could not get corehvt_fck\n");
return PTR_ERR(srcore.sen[0].fck);
}
printk("sr1_fck HVT rate = %lu\n",
clk_get_rate(srcore.sen[0].fck));
srcore.sen[1].fck = clk_get(NULL, "smartreflex_coresvt_fck");
if (IS_ERR(srcore.sen[1].fck)) {
printk(KERN_ERR "Could not get coresvt_fck\n");
ret = PTR_ERR(srcore.sen[1].fck);
goto fail_svt_clk_get;
}
printk("sr2_fck SVT rate = %lu\n",
clk_get_rate(srcore.sen[1].fck));
if (sr_debugfs_enable == 1) {
ret = sr_debugfs_entires(&srcore);
if (ret) {
printk(KERN_ERR "Debug FS entires are created\n");
goto fail_hvt_req_irq;
}
} else {
ret = sr_set_nvalues(&srcore);
if (ret) {
printk(KERN_WARNING "SmartReflex Driver is not"
"initialized\n");
goto fail_hvt_req_irq;
}
}
/* Read current GPIO value and voltage */
srcore.init_gpio_val = sr_gpio_getn();
srcore.curr_volt = get_core_voltage();
ret = request_irq(TI81XX_IRQ_SMRFLX0, srcore_class2_irq,
IRQF_DISABLED, "sr1", NULL);
if (ret) {
printk(KERN_ERR "Could not install SR1 ISR\n");
goto fail_hvt_req_irq;
}
ret = request_irq(TI81XX_IRQ_SMRFLX1, srcore_class2_irq,
IRQF_DISABLED, "sr2", NULL);
if (ret) {
printk(KERN_ERR "Could not install SR2 ISR\n");
goto fail_svt_req_irq;
}
ret = sysfs_create_file(power_kobj, &sr_core_autocomp.attr);
if (ret) {
printk(KERN_ERR "subsys_create_file failed: %d\n", ret);
goto fail_sysfs;
}
printk("SmartReflex Driver initialized\n");
return 0;
fail_sysfs:
free_irq(TI81XX_IRQ_SMRFLX1, NULL);
fail_svt_req_irq:
free_irq(TI81XX_IRQ_SMRFLX0, NULL);
fail_hvt_req_irq:
clk_put(srcore.sen[1].fck);
fail_svt_clk_get:
clk_put(srcore.sen[0].fck);
return ret;
}
static void __exit sr_class2_exit(void)
{
clk_put(srcore.sen[0].fck);
clk_put(srcore.sen[1].fck);
free_irq(TI81XX_IRQ_SMRFLX0, NULL);
free_irq(TI81XX_IRQ_SMRFLX1, NULL);
sysfs_remove_file(power_kobj, &sr_core_autocomp.attr);
}
MODULE_AUTHOR("Texas Instruments, Inc.");
MODULE_DESCRIPTION("SmartReflex Class2 driver");
MODULE_LICENSE("GPL");
module_init(sr_class2_init);
module_exit(sr_class2_exit);