blob: 3ad25b00e5c48066be6697a0868ecf01c9341b0c [file] [log] [blame]
/*
* MARVELL BERLIN OPP related functions
*
* Author: Jisheng Zhang <jszhang@marvell.com>
* Copyright (c) 2014 Marvell Technology Group Ltd.
* http://www.marvell.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/cpu.h>
#include <linux/opp.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/export.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
// VARIANT_BERLIN2CDP is for products which cpu can run up to 1.3 GHz
// VARIANT_BERLIN2CDP_V2 products can run up to 1.5 GHz
#define VARIANT_BERLIN2Q 0
#define VARIANT_BERLIN2CDP 1
#define VARIANT_BERLIN2CDP_V2 2
struct leakage_volt {
int leakage;
unsigned long vh_volt;
unsigned long vl_volt;
};
static struct leakage_volt berlin2q_lv[] = {
{1729, 1150000, 1125000},
{1513, 1175000, 1150000},
{1321, 1200000, 1175000},
{1129, 1225000, 1200000},
{913, 1250000, 1225000},
{601, 1275000, 1250000},
{313, 1300000, 1275000},
{0, 1325000, 1300000},
};
static struct leakage_volt berlin2cdp_lv[] = {
{353, 1075000, 1025000},
{305, 1100000, 1050000},
{257, 1125000, 1075000},
{225, 1150000, 1100000},
{193, 1175000, 1125000},
{0, 1200000, 1150000},
};
static unsigned long berlin2q_freq[] = {600000000, 800000000, 1000000000, 1200000000};
static unsigned long berlin2cdp_freq[] = {600000000, 800000000, 1000000000, 1300000000};
static unsigned long berlin2cdp_v2_freq[] = {600000000, 800000000, 1000000000, 1300000000, 1500000000};
static int berlin_init_opp_table(unsigned long *freq_table, int array_num,
unsigned long vh_volt, unsigned long vl_volt)
{
int i, ret = 0;
struct device_node *np;
struct device *cpu_dev;
np = of_find_node_by_path("/cpus/cpu@0");
if (!np) {
pr_err("failed to find cpu0 node\n");
return -ENODEV;
}
cpu_dev = get_cpu_device(0);
if (!cpu_dev) {
pr_err("failed to get cpu0 device\n");
ret = -ENODEV;
goto out_put_node;
}
cpu_dev->of_node = np;
for (i = 0; i < array_num; i++) {
if (freq_table[i] > 1300000000) {
opp_add(cpu_dev, freq_table[i], vh_volt);
pr_info("opp add freq %d, volt %d\n", freq_table[i], vh_volt);
}
else if (freq_table[i] > 1000000000) {
opp_add(cpu_dev, freq_table[i], (vh_volt - 50000));
pr_info("opp add freq %d, volt %d\n", freq_table[i], (vh_volt - 50000));
}
else {
opp_add(cpu_dev, freq_table[i], vl_volt);
pr_info("opp add freq %d, volt %d\n", freq_table[i], vl_volt);
}
}
platform_device_register_simple("cpufreq-cpu0", -1, NULL, 0);
out_put_node:
of_node_put(np);
return ret;
}
static int get_volt(const struct of_device_id *of_id, int leakage,
unsigned long *vh_volt, unsigned long *vl_volt)
{
int variant, i;
if (!of_id)
return -EINVAL;
variant = (int)of_id->data;
switch (variant) {
case VARIANT_BERLIN2Q:
leakage = (leakage >> 8) & 0xFF;
leakage <<= 4;
for (i = 0; i < ARRAY_SIZE(berlin2q_lv); i++) {
if (berlin2q_lv[i].leakage <= leakage)
break;
}
if (i < ARRAY_SIZE(berlin2q_lv)) {
*vh_volt = berlin2q_lv[i].vh_volt;
*vl_volt = berlin2q_lv[i].vl_volt;
}
break;
case VARIANT_BERLIN2CDP:
case VARIANT_BERLIN2CDP_V2:
for (i = 0; i < ARRAY_SIZE(berlin2cdp_lv); i++) {
if (berlin2cdp_lv[i].leakage <= leakage)
break;
}
if (i < ARRAY_SIZE(berlin2cdp_lv)) {
*vh_volt = berlin2cdp_lv[i].vh_volt;
*vl_volt = berlin2cdp_lv[i].vl_volt;
}
break;
default:
return -EINVAL;
}
printk("leakage: %d\n", leakage);
return 0;
}
static int get_ft(const struct of_device_id *of_id, unsigned long **freq_table,
int *array_num)
{
int variant;
if (!of_id)
return -EINVAL;
variant = (int)of_id->data;
switch (variant) {
case VARIANT_BERLIN2Q:
*freq_table = berlin2q_freq;
*array_num = ARRAY_SIZE(berlin2q_freq);
break;
case VARIANT_BERLIN2CDP:
*freq_table = berlin2cdp_freq;
*array_num = ARRAY_SIZE(berlin2cdp_freq);
break;
case VARIANT_BERLIN2CDP_V2:
*freq_table = berlin2cdp_v2_freq;
*array_num = ARRAY_SIZE(berlin2cdp_v2_freq);
break;
default:
return -EINVAL;
}
return 0;
}
static struct of_device_id berlin_opp_match[] = {
{
.compatible = "marvell,berlin2q-opp",
.data = (void *)VARIANT_BERLIN2Q
},
{
.compatible = "marvell,berlin2cdp-opp",
.data = (void *)VARIANT_BERLIN2CDP
},
{
.compatible = "marvell,berlin2cdp-v2-opp",
.data = (void *)VARIANT_BERLIN2CDP_V2
},
{},
};
static int berlin_opp_probe(struct platform_device *pdev)
{
void __iomem *otp_base;
int leakage, array_num;
unsigned long *freq_table;
unsigned long vh_volt = 1150000;
unsigned long vl_volt = 1150000;
const struct of_device_id *of_id;
struct device_node *np = pdev->dev.of_node;
of_id = of_match_device(berlin_opp_match, &pdev->dev);
otp_base = of_iomap(np, 0);
if (!otp_base)
goto out;
leakage = readl(otp_base);
iounmap(otp_base);
if (get_volt(of_id, leakage, &vh_volt, &vl_volt))
return -EINVAL;
out:
if (get_ft(of_id, &freq_table, &array_num))
return -EINVAL;
return berlin_init_opp_table(freq_table, array_num, vh_volt, vl_volt);
}
static struct platform_driver berlin_opp_driver = {
.probe = berlin_opp_probe,
.driver = {
.name = "berlin_opp",
.owner = THIS_MODULE,
.of_match_table = berlin_opp_match,
},
};
static int __init berlin_init_opp(void)
{
return platform_driver_register(&berlin_opp_driver);
}
late_initcall(berlin_init_opp);