drm/tegra: sor: Implement sor1_brick clock

sor1_brick is a clock that can be used as a source for the sor1 clock.
The registers to control the clock output are part of the sor1 IP block
and hence the sor driver is the best place to implement it.

Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
Thierry Reding 2015-10-01 14:25:03 +02:00
parent aaff8bd2e8
commit b299221ca9
1 changed files with 107 additions and 0 deletions

View File

@ -7,6 +7,7 @@
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/debugfs.h>
#include <linux/gpio.h>
#include <linux/io.h>
@ -170,6 +171,7 @@ struct tegra_sor {
struct reset_control *rst;
struct clk *clk_parent;
struct clk *clk_brick;
struct clk *clk_safe;
struct clk *clk_dp;
struct clk *clk;
@ -255,6 +257,101 @@ static int tegra_sor_set_parent_clock(struct tegra_sor *sor, struct clk *parent)
return 0;
}
struct tegra_clk_sor_brick {
struct clk_hw hw;
struct tegra_sor *sor;
};
static inline struct tegra_clk_sor_brick *to_brick(struct clk_hw *hw)
{
return container_of(hw, struct tegra_clk_sor_brick, hw);
}
static const char * const tegra_clk_sor_brick_parents[] = {
"pll_d2_out0", "pll_dp"
};
static int tegra_clk_sor_brick_set_parent(struct clk_hw *hw, u8 index)
{
struct tegra_clk_sor_brick *brick = to_brick(hw);
struct tegra_sor *sor = brick->sor;
u32 value;
value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK;
switch (index) {
case 0:
value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK;
break;
case 1:
value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK;
break;
}
tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
return 0;
}
static u8 tegra_clk_sor_brick_get_parent(struct clk_hw *hw)
{
struct tegra_clk_sor_brick *brick = to_brick(hw);
struct tegra_sor *sor = brick->sor;
u8 parent = U8_MAX;
u32 value;
value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
switch (value & SOR_CLK_CNTRL_DP_CLK_SEL_MASK) {
case SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK:
case SOR_CLK_CNTRL_DP_CLK_SEL_DIFF_PCLK:
parent = 0;
break;
case SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK:
case SOR_CLK_CNTRL_DP_CLK_SEL_DIFF_DPCLK:
parent = 1;
break;
}
return parent;
}
static const struct clk_ops tegra_clk_sor_brick_ops = {
.set_parent = tegra_clk_sor_brick_set_parent,
.get_parent = tegra_clk_sor_brick_get_parent,
};
static struct clk *tegra_clk_sor_brick_register(struct tegra_sor *sor,
const char *name)
{
struct tegra_clk_sor_brick *brick;
struct clk_init_data init;
struct clk *clk;
brick = devm_kzalloc(sor->dev, sizeof(*brick), GFP_KERNEL);
if (!brick)
return ERR_PTR(-ENOMEM);
brick->sor = sor;
init.name = name;
init.flags = 0;
init.parent_names = tegra_clk_sor_brick_parents;
init.num_parents = ARRAY_SIZE(tegra_clk_sor_brick_parents);
init.ops = &tegra_clk_sor_brick_ops;
brick->hw.init = &init;
clk = devm_clk_register(sor->dev, &brick->hw);
if (IS_ERR(clk))
kfree(brick);
return clk;
}
static int tegra_sor_dp_train_fast(struct tegra_sor *sor,
struct drm_dp_link *link)
{
@ -2522,6 +2619,16 @@ static int tegra_sor_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, sor);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
sor->clk_brick = tegra_clk_sor_brick_register(sor, "sor1_brick");
pm_runtime_put(&pdev->dev);
if (IS_ERR(sor->clk_brick)) {
err = PTR_ERR(sor->clk_brick);
dev_err(&pdev->dev, "failed to register SOR clock: %d\n", err);
goto remove;
}
INIT_LIST_HEAD(&sor->client.list);
sor->client.ops = &sor_client_ops;
sor->client.dev = &pdev->dev;