573 lines
13 KiB
C
573 lines
13 KiB
C
/*
|
|
* Copyright (c) 2015, The Linux Foundation. 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.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/err.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/io.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/clk/msm-clk-provider.h>
|
|
#include <linux/clk/msm-clock-generic.h>
|
|
#include <linux/debugfs.h>
|
|
|
|
#define CMD_RCGR_REG 0x0
|
|
#define CMD_UPDATE_EN BIT(0)
|
|
/* Async_clk_en */
|
|
#define CMD_ROOT_EN BIT(1)
|
|
|
|
struct rcgwr {
|
|
void __iomem *base;
|
|
void __iomem *rcg_base;
|
|
int *dfs_sid_offset;
|
|
int *dfs_sid_value;
|
|
int dfs_sid_len;
|
|
int *link_sid_offset;
|
|
int *link_sid_value;
|
|
int link_sid_len;
|
|
int *lmh_sid_offset;
|
|
int *lmh_sid_value;
|
|
int lmh_sid_len;
|
|
bool inited;
|
|
};
|
|
|
|
static struct rcgwr **rcgwr;
|
|
static struct platform_device *cpu_clock_dev;
|
|
static u32 num_clusters;
|
|
|
|
#define DFS_SID_1_2 0x10
|
|
#define DFS_SID_3_4 0x14
|
|
#define DFS_SID_5_6 0x18
|
|
#define DFS_SID_7_8 0x1C
|
|
#define DFS_SID_9_10 0x20
|
|
#define DFS_SID_11_12 0x24
|
|
#define DFS_SID_13_14 0x28
|
|
#define DFS_SID_15 0x2C
|
|
#define LMH_SID_1_2 0x30
|
|
#define LMH_SID_3_4 0x34
|
|
#define LMH_SID_5 0x38
|
|
#define DCVS_CFG_CTL 0x50
|
|
#define LMH_CFG_CTL 0x54
|
|
#define RC_CFG_CTL 0x58
|
|
#define RC_CFG_DBG 0x5C
|
|
#define RC_CFG_UPDATE 0x60
|
|
|
|
#define RC_CFG_UPDATE_EN_BIT 8
|
|
#define RC_CFG_ACK_BIT 16
|
|
|
|
#define UPDATE_CHECK_MAX_LOOPS 500
|
|
|
|
#define DFS_SID_START 0xE
|
|
#define LMH_SID_START 0x6
|
|
#define DCVS_CONFIG 0x2
|
|
#define LINK_SID 0x3
|
|
|
|
/* Sequence for enable */
|
|
static int ramp_en[] = { 0x800, 0xC00, 0x400};
|
|
|
|
static int check_rcg_config(void __iomem *base)
|
|
{
|
|
u32 cmd_rcgr_regval, count;
|
|
|
|
cmd_rcgr_regval = readl_relaxed(base + CMD_RCGR_REG);
|
|
cmd_rcgr_regval |= CMD_ROOT_EN;
|
|
writel_relaxed(cmd_rcgr_regval, (base + CMD_RCGR_REG));
|
|
|
|
for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) {
|
|
cmd_rcgr_regval = readl_relaxed(base + CMD_RCGR_REG);
|
|
cmd_rcgr_regval &= CMD_UPDATE_EN;
|
|
if (!(cmd_rcgr_regval)) {
|
|
pr_debug("cmd_rcgr state on update bit cleared 0x%x, cmd 0x%x\n",
|
|
readl_relaxed(base + CMD_RCGR_REG),
|
|
cmd_rcgr_regval);
|
|
return 0;
|
|
}
|
|
udelay(1);
|
|
}
|
|
|
|
BUG_ON(count == 0);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int rc_config_update(void __iomem *base, u32 rc_value, u32 rc_ack_bit)
|
|
{
|
|
u32 count, ret = 0, regval;
|
|
|
|
regval = readl_relaxed(base + RC_CFG_UPDATE);
|
|
regval |= rc_value;
|
|
writel_relaxed(regval, base + RC_CFG_UPDATE);
|
|
regval |= BIT(RC_CFG_UPDATE_EN_BIT);
|
|
writel_relaxed(regval, base + RC_CFG_UPDATE);
|
|
|
|
/* Poll for update ack */
|
|
for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) {
|
|
regval = readl_relaxed((base + RC_CFG_UPDATE))
|
|
>> RC_CFG_ACK_BIT;
|
|
if (regval == BIT(rc_ack_bit)) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
udelay(1);
|
|
}
|
|
BUG_ON(count == 0);
|
|
|
|
/* Clear RC_CFG_UPDATE_EN */
|
|
writel_relaxed(0 << RC_CFG_UPDATE_EN_BIT, (base + RC_CFG_UPDATE));
|
|
/* Poll for update ack */
|
|
for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) {
|
|
regval = readl_relaxed((base + RC_CFG_UPDATE))
|
|
>> RC_CFG_ACK_BIT;
|
|
if (!regval)
|
|
return ret;
|
|
udelay(1);
|
|
}
|
|
BUG_ON(count == 0);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
static int ramp_control_enable(struct platform_device *pdev,
|
|
struct rcgwr *rcgwr)
|
|
{
|
|
int i = 0, ret = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ramp_en); i++) {
|
|
ret = check_rcg_config(rcgwr->rcg_base);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to update config!!!\n");
|
|
return ret;
|
|
}
|
|
writel_relaxed(ramp_en[i], rcgwr->base + DCVS_CFG_CTL);
|
|
ret = rc_config_update(rcgwr->base, DCVS_CONFIG, DCVS_CONFIG);
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"Failed to config update for 0x2 and ACK 0x4\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ramp_down_disable(struct platform_device *pdev,
|
|
struct rcgwr *rcgwr)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = check_rcg_config(rcgwr->rcg_base);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to update config!!!\n");
|
|
return ret;
|
|
}
|
|
|
|
writel_relaxed(0x200, rcgwr->base + DCVS_CFG_CTL);
|
|
ret = rc_config_update(rcgwr->base, DCVS_CONFIG, DCVS_CONFIG);
|
|
if (ret)
|
|
dev_err(&pdev->dev,
|
|
"Failed to config update for 0x2 and ACK 0x4\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ramp_control_disable(struct platform_device *pdev,
|
|
struct rcgwr *rcgwr)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!rcgwr->inited)
|
|
return 0;
|
|
|
|
ret = check_rcg_config(rcgwr->rcg_base);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to update config!!!\n");
|
|
return ret;
|
|
}
|
|
|
|
writel_relaxed(0x0, rcgwr->base + DCVS_CFG_CTL);
|
|
|
|
ret = rc_config_update(rcgwr->base, DCVS_CONFIG, DCVS_CONFIG);
|
|
if (ret)
|
|
dev_err(&pdev->dev,
|
|
"Failed to config update for 0x2 and ACK 0x4\n");
|
|
|
|
rcgwr->inited = false;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ramp_link_sid(struct platform_device *pdev, struct rcgwr *rcgwr)
|
|
{
|
|
int ret = 0, i;
|
|
|
|
if (!rcgwr->link_sid_len) {
|
|
pr_err("Use Default Link SID\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = check_rcg_config(rcgwr->rcg_base);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to update config!!!\n");
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < rcgwr->link_sid_len; i++)
|
|
writel_relaxed(rcgwr->link_sid_value[i],
|
|
rcgwr->base + rcgwr->link_sid_offset[i]);
|
|
|
|
ret = rc_config_update(rcgwr->base, LINK_SID, LINK_SID);
|
|
if (ret)
|
|
dev_err(&pdev->dev,
|
|
"Failed to config update for 0x3 and ACK 0x8\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ramp_lmh_sid(struct platform_device *pdev, struct rcgwr *rcgwr)
|
|
{
|
|
int ret = 0, i, j;
|
|
|
|
if (!rcgwr->lmh_sid_len) {
|
|
pr_err("Use Default LMH SID\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = check_rcg_config(rcgwr->rcg_base);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to update config!!!\n");
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < rcgwr->lmh_sid_len; i++)
|
|
writel_relaxed(rcgwr->lmh_sid_value[i],
|
|
rcgwr->base + rcgwr->lmh_sid_offset[i]);
|
|
|
|
for (i = LMH_SID_START, j = 0; j < rcgwr->lmh_sid_len; i--, j++) {
|
|
ret = rc_config_update(rcgwr->base, i, i);
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"Failed to update config for DFSSID-0x%x and ack 0x%lx\n",
|
|
i, BIT(i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ramp_dfs_sid(struct platform_device *pdev, struct rcgwr *rcgwr)
|
|
{
|
|
int ret = 0, i, j;
|
|
|
|
if (!rcgwr->dfs_sid_len) {
|
|
pr_err("Use Default DFS SID\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = check_rcg_config(rcgwr->rcg_base);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to update config!!!\n");
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < rcgwr->dfs_sid_len; i++)
|
|
writel_relaxed(rcgwr->dfs_sid_value[i],
|
|
rcgwr->base + rcgwr->dfs_sid_offset[i]);
|
|
|
|
for (i = DFS_SID_START, j = 0; j < rcgwr->dfs_sid_len; i--, j++) {
|
|
ret = rc_config_update(rcgwr->base, i, i);
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"Failed to update config for DFSSID-0x%x and ack 0x%lx\n",
|
|
i, BIT(i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int parse_dt_rcgwr(struct platform_device *pdev, char *prop_name,
|
|
int **off, int **val, int *len)
|
|
{
|
|
struct device_node *node = pdev->dev.of_node;
|
|
int prop_len, i;
|
|
u32 *array;
|
|
|
|
if (!of_find_property(node, prop_name, &prop_len)) {
|
|
dev_err(&pdev->dev, "missing %s\n", prop_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
prop_len /= sizeof(u32);
|
|
if (prop_len % 2) {
|
|
dev_err(&pdev->dev, "bad length %d\n", prop_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
prop_len /= 2;
|
|
|
|
*off = devm_kzalloc(&pdev->dev, prop_len * sizeof(u32), GFP_KERNEL);
|
|
if (!*off)
|
|
return -ENOMEM;
|
|
|
|
*val = devm_kzalloc(&pdev->dev, prop_len * sizeof(u32), GFP_KERNEL);
|
|
if (!*val)
|
|
return -ENOMEM;
|
|
|
|
array = devm_kzalloc(&pdev->dev,
|
|
prop_len * sizeof(u32) * 2, GFP_KERNEL);
|
|
if (!array)
|
|
return -ENOMEM;
|
|
|
|
of_property_read_u32_array(node, prop_name, array, prop_len * 2);
|
|
for (i = 0; i < prop_len; i++) {
|
|
*(*off + i) = array[i * 2];
|
|
*(*val + i) = array[2 * i + 1];
|
|
}
|
|
|
|
*len = prop_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rcgwr_init_bases(struct platform_device *pdev, struct rcgwr *rcgwr,
|
|
const char *name)
|
|
{
|
|
struct resource *res;
|
|
char rcg_name[] = "rcgwr-xxx-base";
|
|
char rcg_mux[] = "xxx-mux";
|
|
|
|
snprintf(rcg_name, ARRAY_SIZE(rcg_name), "rcgwr-%s-base", name);
|
|
res = platform_get_resource_byname(pdev,
|
|
IORESOURCE_MEM, rcg_name);
|
|
if (!res) {
|
|
dev_err(&pdev->dev, "missing %s\n", rcg_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rcgwr->base = devm_ioremap(&pdev->dev, res->start,
|
|
resource_size(res));
|
|
if (!rcgwr->base) {
|
|
dev_err(&pdev->dev, "ioremap failed for %s\n",
|
|
rcg_name);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
snprintf(rcg_mux, ARRAY_SIZE(rcg_mux), "%s-mux", name);
|
|
res = platform_get_resource_byname(pdev,
|
|
IORESOURCE_MEM, rcg_mux);
|
|
if (!res) {
|
|
dev_err(&pdev->dev, "missing %s\n", rcg_mux);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rcgwr->rcg_base = devm_ioremap(&pdev->dev, res->start,
|
|
resource_size(res));
|
|
if (!rcgwr->rcg_base) {
|
|
dev_err(&pdev->dev, "ioremap failed for %s\n",
|
|
rcg_name);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Disable the RCG ramp controller.
|
|
*/
|
|
int clock_rcgwr_disable(struct platform_device *pdev)
|
|
{
|
|
int i, ret = 0;
|
|
|
|
for (i = 0; i < num_clusters; i++) {
|
|
if (!rcgwr[i])
|
|
return -ENOMEM;
|
|
ret = ramp_control_disable(pdev, rcgwr[i]);
|
|
if (ret)
|
|
dev_err(&pdev->dev,
|
|
"Ramp controller disable failed for Cluster-%d\n", i);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int clock_rcgwr_disable_set(void *data, u64 val)
|
|
{
|
|
if (val) {
|
|
pr_err("Enabling not supported!!\n");
|
|
return -EINVAL;
|
|
} else
|
|
return clock_rcgwr_disable(cpu_clock_dev);
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(rcgwr_enable_fops, NULL,
|
|
clock_rcgwr_disable_set, "%lld\n");
|
|
|
|
static int clock_debug_enable_show(struct seq_file *m, void *v)
|
|
{
|
|
int i = 0;
|
|
|
|
seq_puts(m, "Cluster\t\tEnable\n");
|
|
|
|
for (i = 0; i < num_clusters; i++)
|
|
seq_printf(m, "%d\t\t%d\n", i, rcgwr[i]->inited);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int clock_debug_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, clock_debug_enable_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations rcgwr_enable_show = {
|
|
.owner = THIS_MODULE,
|
|
.open = clock_debug_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
/*
|
|
* Program the DFS Sequence ID.
|
|
* Program the Link Sequence ID.
|
|
* Enable RCG with ramp controller.
|
|
*/
|
|
int clock_rcgwr_init(struct platform_device *pdev)
|
|
{
|
|
int ret = 0, i;
|
|
char link_sid[] = "qcom,link-sid-xxx";
|
|
char dfs_sid[] = "qcom,dfs-sid-xxx";
|
|
char lmh_sid[] = "qcom,lmh-sid-xxx";
|
|
char ramp_dis[] = "qcom,ramp-dis-xxx";
|
|
char names[] = "cxxx";
|
|
struct dentry *debugfs_base;
|
|
|
|
ret = of_property_read_u32(pdev->dev.of_node, "qcom,num-clusters",
|
|
&num_clusters);
|
|
if (ret)
|
|
panic("Cannot read num-clusters from dt (ret:%d)\n", ret);
|
|
|
|
rcgwr = devm_kzalloc(&pdev->dev, sizeof(struct rcgwr) * num_clusters,
|
|
GFP_KERNEL);
|
|
if (!rcgwr)
|
|
BUG();
|
|
|
|
for (i = 0; i < num_clusters; i++) {
|
|
rcgwr[i] = devm_kzalloc(&pdev->dev, sizeof(struct rcgwr),
|
|
GFP_KERNEL);
|
|
if (!rcgwr[i])
|
|
goto fail_mem;
|
|
|
|
snprintf(names, ARRAY_SIZE(names), "c%d", i);
|
|
|
|
ret = rcgwr_init_bases(pdev, rcgwr[i], names);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to init_bases for RCGwR\n");
|
|
goto fail_mem;
|
|
}
|
|
|
|
snprintf(dfs_sid, ARRAY_SIZE(dfs_sid),
|
|
"qcom,dfs-sid-%s", names);
|
|
ret = parse_dt_rcgwr(pdev, dfs_sid, &(rcgwr[i]->dfs_sid_offset),
|
|
&(rcgwr[i]->dfs_sid_value), &(rcgwr[i]->dfs_sid_len));
|
|
if (ret)
|
|
dev_err(&pdev->dev,
|
|
"No DFS SID tables found for Cluster-%d\n", i);
|
|
|
|
snprintf(link_sid, ARRAY_SIZE(link_sid),
|
|
"qcom,link-sid-%s", names);
|
|
ret = parse_dt_rcgwr(pdev, link_sid,
|
|
&(rcgwr[i]->link_sid_offset),
|
|
&(rcgwr[i]->link_sid_value), &(rcgwr[i]->link_sid_len));
|
|
if (ret)
|
|
dev_err(&pdev->dev,
|
|
"No Link SID tables found for Cluster-%d\n", i);
|
|
|
|
snprintf(lmh_sid, ARRAY_SIZE(lmh_sid),
|
|
"qcom,lmh-sid-%s", names);
|
|
ret = parse_dt_rcgwr(pdev, lmh_sid,
|
|
&(rcgwr[i]->lmh_sid_offset),
|
|
&(rcgwr[i]->lmh_sid_value), &(rcgwr[i]->lmh_sid_len));
|
|
if (ret)
|
|
dev_err(&pdev->dev,
|
|
"No LMH SID tables found for Cluster-%d\n", i);
|
|
|
|
ret = ramp_lmh_sid(pdev, rcgwr[i]);
|
|
if (ret)
|
|
goto fail_mem;
|
|
|
|
ret = ramp_dfs_sid(pdev, rcgwr[i]);
|
|
if (ret)
|
|
goto fail_mem;
|
|
|
|
ret = ramp_link_sid(pdev, rcgwr[i]);
|
|
if (ret)
|
|
goto fail_mem;
|
|
|
|
ret = ramp_control_enable(pdev, rcgwr[i]);
|
|
if (ret)
|
|
goto fail_mem;
|
|
|
|
snprintf(ramp_dis, ARRAY_SIZE(ramp_dis),
|
|
"qcom,ramp-dis-%s", names);
|
|
if (of_property_read_bool(pdev->dev.of_node, ramp_dis)) {
|
|
ret = ramp_down_disable(pdev, rcgwr[i]);
|
|
if (ret)
|
|
goto fail_mem;
|
|
}
|
|
|
|
rcgwr[i]->inited = true;
|
|
}
|
|
|
|
cpu_clock_dev = pdev;
|
|
|
|
debugfs_base = debugfs_create_dir("rcgwr", NULL);
|
|
if (debugfs_base) {
|
|
if (!debugfs_create_file("enable", S_IRUGO, debugfs_base, NULL,
|
|
&rcgwr_enable_fops)) {
|
|
pr_err("Unable to create `enable` debugfs entry\n");
|
|
debugfs_remove(debugfs_base);
|
|
}
|
|
|
|
if (!debugfs_create_file("status", 0444, debugfs_base, NULL,
|
|
&rcgwr_enable_show)) {
|
|
pr_err("Unable to create `status` debugfs entry\n");
|
|
debugfs_remove_recursive(debugfs_base);
|
|
}
|
|
} else
|
|
pr_err("Unable to create debugfs dir\n");
|
|
|
|
pr_info("RCGwR Init Completed\n");
|
|
|
|
return ret;
|
|
|
|
fail_mem:
|
|
--i;
|
|
for (; i >= 0 ; i--) {
|
|
devm_kfree(&pdev->dev, rcgwr[i]);
|
|
rcgwr[i] = NULL;
|
|
}
|
|
devm_kfree(&pdev->dev, rcgwr);
|
|
panic("RCGwR failed to Initialize\n");
|
|
}
|