summaryrefslogtreecommitdiff
path: root/drivers/clk/meson/meson8-ddr.c
blob: 4b73ea244b630fb4cfb6525a8cb4848c987a8107 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// SPDX-License-Identifier: GPL-2.0+
/*
 * Amlogic Meson8 DDR clock controller
 *
 * Copyright (C) 2019 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
 */

#include <dt-bindings/clock/meson8-ddr-clkc.h>

#include <linux/clk-provider.h>
#include <linux/platform_device.h>

#include "clk-regmap.h"
#include "clk-pll.h"

#define AM_DDR_PLL_CNTL			0x00
#define AM_DDR_PLL_CNTL1		0x04
#define AM_DDR_PLL_CNTL2		0x08
#define AM_DDR_PLL_CNTL3		0x0c
#define AM_DDR_PLL_CNTL4		0x10
#define AM_DDR_PLL_STS			0x14
#define DDR_CLK_CNTL			0x18
#define DDR_CLK_STS			0x1c

static struct clk_regmap meson8_ddr_pll_dco = {
	.data = &(struct meson_clk_pll_data){
		.en = {
			.reg_off = AM_DDR_PLL_CNTL,
			.shift   = 30,
			.width   = 1,
		},
		.m = {
			.reg_off = AM_DDR_PLL_CNTL,
			.shift   = 0,
			.width   = 9,
		},
		.n = {
			.reg_off = AM_DDR_PLL_CNTL,
			.shift   = 9,
			.width   = 5,
		},
		.l = {
			.reg_off = AM_DDR_PLL_CNTL,
			.shift   = 31,
			.width   = 1,
		},
		.rst = {
			.reg_off = AM_DDR_PLL_CNTL,
			.shift   = 29,
			.width   = 1,
		},
	},
	.hw.init = &(struct clk_init_data){
		.name = "ddr_pll_dco",
		.ops = &meson_clk_pll_ro_ops,
		.parent_data = &(const struct clk_parent_data) {
			.fw_name = "xtal",
		},
		.num_parents = 1,
	},
};

static struct clk_regmap meson8_ddr_pll = {
	.data = &(struct clk_regmap_div_data){
		.offset = AM_DDR_PLL_CNTL,
		.shift = 16,
		.width = 2,
		.flags = CLK_DIVIDER_POWER_OF_TWO,
	},
	.hw.init = &(struct clk_init_data){
		.name = "ddr_pll",
		.ops = &clk_regmap_divider_ro_ops,
		.parent_hws = (const struct clk_hw *[]) {
			&meson8_ddr_pll_dco.hw
		},
		.num_parents = 1,
	},
};

static struct clk_hw_onecell_data meson8_ddr_clk_hw_onecell_data = {
	.hws = {
		[DDR_CLKID_DDR_PLL_DCO]		= &meson8_ddr_pll_dco.hw,
		[DDR_CLKID_DDR_PLL]		= &meson8_ddr_pll.hw,
	},
	.num = 2,
};

static struct clk_regmap *const meson8_ddr_clk_regmaps[] = {
	&meson8_ddr_pll_dco,
	&meson8_ddr_pll,
};

static const struct regmap_config meson8_ddr_clkc_regmap_config = {
	.reg_bits = 8,
	.val_bits = 32,
	.reg_stride = 4,
	.max_register = DDR_CLK_STS,
};

static int meson8_ddr_clkc_probe(struct platform_device *pdev)
{
	struct regmap *regmap;
	void __iomem *base;
	struct clk_hw *hw;
	int ret, i;

	base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(base))
		return PTR_ERR(base);

	regmap = devm_regmap_init_mmio(&pdev->dev, base,
				       &meson8_ddr_clkc_regmap_config);
	if (IS_ERR(regmap))
		return PTR_ERR(regmap);

	/* Populate regmap */
	for (i = 0; i < ARRAY_SIZE(meson8_ddr_clk_regmaps); i++)
		meson8_ddr_clk_regmaps[i]->map = regmap;

	/* Register all clks */
	for (i = 0; i < meson8_ddr_clk_hw_onecell_data.num; i++) {
		hw = meson8_ddr_clk_hw_onecell_data.hws[i];

		ret = devm_clk_hw_register(&pdev->dev, hw);
		if (ret) {
			dev_err(&pdev->dev, "Clock registration failed\n");
			return ret;
		}
	}

	return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get,
					   &meson8_ddr_clk_hw_onecell_data);
}

static const struct of_device_id meson8_ddr_clkc_match_table[] = {
	{ .compatible = "amlogic,meson8-ddr-clkc" },
	{ .compatible = "amlogic,meson8b-ddr-clkc" },
	{ /* sentinel */ }
};

static struct platform_driver meson8_ddr_clkc_driver = {
	.probe		= meson8_ddr_clkc_probe,
	.driver		= {
		.name	= "meson8-ddr-clkc",
		.of_match_table = meson8_ddr_clkc_match_table,
	},
};

builtin_platform_driver(meson8_ddr_clkc_driver);