blob: 75f8e042cd8affdb09ab09f22c528ba4fa0282fb [file] [log] [blame]
/*
* Copyright (c) 2015, 2017 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);
}
WARN_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);
}
WARN_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);
}
WARN_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)
return -ENOMEM;
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", 0444, 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");
}