blob: 44b332e3f1e5b5d737f6376f6caf99de9b84a215 [file] [log] [blame]
/* Copyright (c) 2013, 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.
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/iopoll.h>
#include <linux/clk.h>
#include <asm/processor.h>
#include <mach/msm_iomap.h>
#include <mach/clk-provider.h>
#include "clock-dsi-8610.h"
#define DSI_PHY_PHYS 0xFDD00000
#define DSI_PHY_SIZE 0x00100000
#define DSI_CTRL 0x0000
#define DSI_DSIPHY_PLL_CTRL_0 0x0200
#define DSI_DSIPHY_PLL_CTRL_1 0x0204
#define DSI_DSIPHY_PLL_CTRL_2 0x0208
#define DSI_DSIPHY_PLL_CTRL_3 0x020C
#define DSI_DSIPHY_PLL_RDY 0x0280
#define DSI_DSIPHY_PLL_CTRL_8 0x0220
#define DSI_DSIPHY_PLL_CTRL_9 0x0224
#define DSI_DSIPHY_PLL_CTRL_10 0x0228
#define DSI_BPP 3
#define DSI_PLL_RDY_BIT 0x01
#define DSI_PLL_RDY_LOOP_COUNT 80000
#define DSI_MAX_DIVIDER 256
static unsigned char *dsi_base;
static struct clk *dsi_ahb_clk;
int __init dsi_clk_ctrl_init(struct clk *ahb_clk)
{
dsi_base = ioremap(DSI_PHY_PHYS, DSI_PHY_SIZE);
if (!dsi_base) {
pr_err("unable to remap dsi base\n");
return -ENODEV;
}
dsi_ahb_clk = ahb_clk;
return 0;
}
static int dsi_pll_vco_enable(struct clk *c)
{
u32 status;
int i = 0, ret = 0;
ret = clk_enable(dsi_ahb_clk);
if (ret) {
pr_err("fail to enable dsi ahb clk\n");
return ret;
}
writel_relaxed(0x01, dsi_base + DSI_DSIPHY_PLL_CTRL_0);
do {
status = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_RDY);
} while (!(status & DSI_PLL_RDY_BIT) && (i++ < DSI_PLL_RDY_LOOP_COUNT));
if (!(status & DSI_PLL_RDY_BIT)) {
pr_err("DSI PLL not ready, polling time out!\n");
ret = -ETIMEDOUT;
}
clk_disable(dsi_ahb_clk);
return ret;
}
static void dsi_pll_vco_disable(struct clk *c)
{
int ret;
ret = clk_enable(dsi_ahb_clk);
if (ret) {
pr_err("fail to enable dsi ahb clk\n");
return;
}
writel_relaxed(0x00, dsi_base + DSI_DSIPHY_PLL_CTRL_0);
clk_disable(dsi_ahb_clk);
}
static int dsi_pll_vco_set_rate(struct clk *c, unsigned long rate)
{
int ret;
u32 temp, val;
unsigned long fb_divider;
struct clk *parent = c->parent;
struct dsi_pll_vco_clk *vco_clk =
container_of(c, struct dsi_pll_vco_clk, c);
if (!rate)
return 0;
ret = clk_prepare_enable(dsi_ahb_clk);
if (ret) {
pr_err("fail to enable dsi ahb clk\n");
return ret;
}
temp = rate / 10;
val = parent->rate / 10;
fb_divider = (temp * vco_clk->pref_div_ratio) / val;
fb_divider = fb_divider / 2 - 1;
temp = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_1);
val = (temp & 0xFFFFFF00) | (fb_divider & 0xFF);
writel_relaxed(val, dsi_base + DSI_DSIPHY_PLL_CTRL_1);
temp = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_2);
val = (temp & 0xFFFFFFF8) | ((fb_divider >> 8) & 0x07);
writel_relaxed(val, dsi_base + DSI_DSIPHY_PLL_CTRL_2);
temp = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_3);
val = (temp & 0xFFFFFFC0) | (vco_clk->pref_div_ratio - 1);
writel_relaxed(val, dsi_base + DSI_DSIPHY_PLL_CTRL_3);
clk_disable_unprepare(dsi_ahb_clk);
return 0;
}
/* rate is the bit clk rate */
static long dsi_pll_vco_round_rate(struct clk *c, unsigned long rate)
{
long vco_rate;
struct dsi_pll_vco_clk *vco_clk =
container_of(c, struct dsi_pll_vco_clk, c);
vco_rate = rate;
if (rate < vco_clk->vco_clk_min)
vco_rate = vco_clk->vco_clk_min;
else if (rate > vco_clk->vco_clk_max)
vco_rate = vco_clk->vco_clk_max;
return vco_rate;
}
static unsigned long dsi_pll_vco_get_rate(struct clk *c)
{
u32 fb_divider, ref_divider, vco_rate;
u32 temp, status;
struct clk *parent = c->parent;
status = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_RDY);
if (status & DSI_PLL_RDY_BIT) {
fb_divider = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_1);
fb_divider &= 0xFF;
temp = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_2) & 0x07;
fb_divider = (temp << 8) | fb_divider;
fb_divider += 1;
ref_divider = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_3);
ref_divider &= 0x3F;
ref_divider += 1;
vco_rate = (parent->rate / ref_divider) * fb_divider;
} else {
vco_rate = 0;
}
return vco_rate;
}
static enum handoff dsi_pll_vco_handoff(struct clk *c)
{
u32 status;
if (clk_prepare_enable(dsi_ahb_clk)) {
pr_err("fail to enable dsi ahb clk\n");
return HANDOFF_DISABLED_CLK;
}
status = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_0);
if (!status & DSI_PLL_RDY_BIT) {
pr_err("DSI PLL not ready\n");
clk_disable(dsi_ahb_clk);
return HANDOFF_DISABLED_CLK;
}
c->rate = dsi_pll_vco_get_rate(c);
clk_disable_unprepare(dsi_ahb_clk);
return HANDOFF_ENABLED_CLK;
}
static int dsi_byteclk_set_rate(struct clk *c, unsigned long rate)
{
int div, ret;
long vco_rate;
unsigned long bitclk_rate;
u32 temp, val;
struct clk *parent = clk_get_parent(c);
if (rate == 0) {
ret = clk_set_rate(parent, 0);
return ret;
}
bitclk_rate = rate * 8;
for (div = 1; div < DSI_MAX_DIVIDER; div++) {
vco_rate = clk_round_rate(parent, bitclk_rate * div);
if (vco_rate == bitclk_rate * div)
break;
if (vco_rate < bitclk_rate * div)
return -EINVAL;
}
if (vco_rate != bitclk_rate * div)
return -EINVAL;
ret = clk_set_rate(parent, vco_rate);
if (ret) {
pr_err("fail to set vco rate\n");
return ret;
}
ret = clk_prepare_enable(dsi_ahb_clk);
if (ret) {
pr_err("fail to enable dsi ahb clk\n");
return ret;
}
/* set the bit clk divider */
temp = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_8);
val = (temp & 0xFFFFFFF0) | (div - 1);
writel_relaxed(val, dsi_base + DSI_DSIPHY_PLL_CTRL_8);
/* set the byte clk divider */
temp = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_9);
val = (temp & 0xFFFFFF00) | (vco_rate / rate - 1);
writel_relaxed(val, dsi_base + DSI_DSIPHY_PLL_CTRL_9);
clk_disable_unprepare(dsi_ahb_clk);
return 0;
}
static long dsi_byteclk_round_rate(struct clk *c, unsigned long rate)
{
int div;
long vco_rate;
unsigned long bitclk_rate;
struct clk *parent = clk_get_parent(c);
if (rate == 0)
return -EINVAL;
bitclk_rate = rate * 8;
for (div = 1; div < DSI_MAX_DIVIDER; div++) {
vco_rate = clk_round_rate(parent, bitclk_rate * div);
if (vco_rate == bitclk_rate * div)
break;
if (vco_rate < bitclk_rate * div)
return -EINVAL;
}
if (vco_rate != bitclk_rate * div)
return -EINVAL;
return rate;
}
static enum handoff dsi_byteclk_handoff(struct clk *c)
{
struct clk *parent = clk_get_parent(c);
unsigned long vco_rate = clk_get_rate(parent);
u32 out_div2;
if (vco_rate == 0)
return HANDOFF_DISABLED_CLK;
if (clk_prepare_enable(dsi_ahb_clk)) {
pr_err("fail to enable dsi ahb clk\n");
return HANDOFF_DISABLED_CLK;
}
out_div2 = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_9);
out_div2 &= 0xFF;
c->rate = vco_rate / (out_div2 + 1);
clk_disable_unprepare(dsi_ahb_clk);
return HANDOFF_ENABLED_CLK;
}
static int dsi_dsiclk_set_rate(struct clk *c, unsigned long rate)
{
u32 temp, val;
int ret;
struct clk *parent = clk_get_parent(c);
unsigned long vco_rate = clk_get_rate(parent);
if (rate == 0)
return 0;
if (vco_rate % rate != 0) {
pr_err("dsiclk_set_rate invalid rate\n");
return -EINVAL;
}
ret = clk_prepare_enable(dsi_ahb_clk);
if (ret) {
pr_err("fail to enable dsi ahb clk\n");
return ret;
}
temp = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_10);
val = (temp & 0xFFFFFF00) | (vco_rate / rate - 1);
writel_relaxed(val, dsi_base + DSI_DSIPHY_PLL_CTRL_10);
clk_disable_unprepare(dsi_ahb_clk);
return 0;
}
static long dsi_dsiclk_round_rate(struct clk *c, unsigned long rate)
{
/* rate is the pixel clk rate, translate into dsi clk rate*/
struct clk *parent = clk_get_parent(c);
unsigned long vco_rate = clk_get_rate(parent);
rate *= DSI_BPP;
if (vco_rate < rate)
return -EINVAL;
if (vco_rate % rate != 0)
return -EINVAL;
return rate;
}
static enum handoff dsi_dsiclk_handoff(struct clk *c)
{
struct clk *parent = clk_get_parent(c);
unsigned long vco_rate = clk_get_rate(parent);
u32 out_div3;
if (vco_rate == 0)
return HANDOFF_DISABLED_CLK;
if (clk_prepare_enable(dsi_ahb_clk)) {
pr_err("fail to enable dsi ahb clk\n");
return HANDOFF_DISABLED_CLK;
}
out_div3 = readl_relaxed(dsi_base + DSI_DSIPHY_PLL_CTRL_10);
out_div3 &= 0xFF;
c->rate = vco_rate / (out_div3 + 1);
clk_disable_unprepare(dsi_ahb_clk);
return HANDOFF_ENABLED_CLK;
}
int dsi_prepare(struct clk *clk)
{
return clk_prepare(dsi_ahb_clk);
}
void dsi_unprepare(struct clk *clk)
{
clk_unprepare(dsi_ahb_clk);
}
struct clk_ops clk_ops_dsi_dsiclk = {
.prepare = dsi_prepare,
.unprepare = dsi_unprepare,
.set_rate = dsi_dsiclk_set_rate,
.round_rate = dsi_dsiclk_round_rate,
.handoff = dsi_dsiclk_handoff,
};
struct clk_ops clk_ops_dsi_byteclk = {
.prepare = dsi_prepare,
.unprepare = dsi_unprepare,
.set_rate = dsi_byteclk_set_rate,
.round_rate = dsi_byteclk_round_rate,
.handoff = dsi_byteclk_handoff,
};
struct clk_ops clk_ops_dsi_vco = {
.prepare = dsi_prepare,
.unprepare = dsi_unprepare,
.enable = dsi_pll_vco_enable,
.disable = dsi_pll_vco_disable,
.set_rate = dsi_pll_vco_set_rate,
.round_rate = dsi_pll_vco_round_rate,
.handoff = dsi_pll_vco_handoff,
};