1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Amlogic Meson8 DDR clock controller
4 *
5 * Copyright (C) 2019 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
6 */
7
8#include <dt-bindings/clock/meson8-ddr-clkc.h>
9
10#include <linux/clk-provider.h>
11#include <linux/platform_device.h>
12
13#include "clk-regmap.h"
14#include "clk-pll.h"
15
16#define AM_DDR_PLL_CNTL			0x00
17#define AM_DDR_PLL_CNTL1		0x04
18#define AM_DDR_PLL_CNTL2		0x08
19#define AM_DDR_PLL_CNTL3		0x0c
20#define AM_DDR_PLL_CNTL4		0x10
21#define AM_DDR_PLL_STS			0x14
22#define DDR_CLK_CNTL			0x18
23#define DDR_CLK_STS			0x1c
24
25static struct clk_regmap meson8_ddr_pll_dco = {
26	.data = &(struct meson_clk_pll_data){
27		.en = {
28			.reg_off = AM_DDR_PLL_CNTL,
29			.shift   = 30,
30			.width   = 1,
31		},
32		.m = {
33			.reg_off = AM_DDR_PLL_CNTL,
34			.shift   = 0,
35			.width   = 9,
36		},
37		.n = {
38			.reg_off = AM_DDR_PLL_CNTL,
39			.shift   = 9,
40			.width   = 5,
41		},
42		.l = {
43			.reg_off = AM_DDR_PLL_CNTL,
44			.shift   = 31,
45			.width   = 1,
46		},
47		.rst = {
48			.reg_off = AM_DDR_PLL_CNTL,
49			.shift   = 29,
50			.width   = 1,
51		},
52	},
53	.hw.init = &(struct clk_init_data){
54		.name = "ddr_pll_dco",
55		.ops = &meson_clk_pll_ro_ops,
56		.parent_data = &(const struct clk_parent_data) {
57			.fw_name = "xtal",
58		},
59		.num_parents = 1,
60	},
61};
62
63static struct clk_regmap meson8_ddr_pll = {
64	.data = &(struct clk_regmap_div_data){
65		.offset = AM_DDR_PLL_CNTL,
66		.shift = 16,
67		.width = 2,
68		.flags = CLK_DIVIDER_POWER_OF_TWO,
69	},
70	.hw.init = &(struct clk_init_data){
71		.name = "ddr_pll",
72		.ops = &clk_regmap_divider_ro_ops,
73		.parent_hws = (const struct clk_hw *[]) {
74			&meson8_ddr_pll_dco.hw
75		},
76		.num_parents = 1,
77	},
78};
79
80static struct clk_hw_onecell_data meson8_ddr_clk_hw_onecell_data = {
81	.hws = {
82		[DDR_CLKID_DDR_PLL_DCO]		= &meson8_ddr_pll_dco.hw,
83		[DDR_CLKID_DDR_PLL]		= &meson8_ddr_pll.hw,
84	},
85	.num = 2,
86};
87
88static struct clk_regmap *const meson8_ddr_clk_regmaps[] = {
89	&meson8_ddr_pll_dco,
90	&meson8_ddr_pll,
91};
92
93static const struct regmap_config meson8_ddr_clkc_regmap_config = {
94	.reg_bits = 8,
95	.val_bits = 32,
96	.reg_stride = 4,
97	.max_register = DDR_CLK_STS,
98};
99
100static int meson8_ddr_clkc_probe(struct platform_device *pdev)
101{
102	struct regmap *regmap;
103	void __iomem *base;
104	struct clk_hw *hw;
105	int ret, i;
106
107	base = devm_platform_ioremap_resource(pdev, 0);
108	if (IS_ERR(base))
109		return PTR_ERR(base);
110
111	regmap = devm_regmap_init_mmio(&pdev->dev, base,
112				       &meson8_ddr_clkc_regmap_config);
113	if (IS_ERR(regmap))
114		return PTR_ERR(regmap);
115
116	/* Populate regmap */
117	for (i = 0; i < ARRAY_SIZE(meson8_ddr_clk_regmaps); i++)
118		meson8_ddr_clk_regmaps[i]->map = regmap;
119
120	/* Register all clks */
121	for (i = 0; i < meson8_ddr_clk_hw_onecell_data.num; i++) {
122		hw = meson8_ddr_clk_hw_onecell_data.hws[i];
123
124		ret = devm_clk_hw_register(&pdev->dev, hw);
125		if (ret) {
126			dev_err(&pdev->dev, "Clock registration failed\n");
127			return ret;
128		}
129	}
130
131	return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get,
132					   &meson8_ddr_clk_hw_onecell_data);
133}
134
135static const struct of_device_id meson8_ddr_clkc_match_table[] = {
136	{ .compatible = "amlogic,meson8-ddr-clkc" },
137	{ .compatible = "amlogic,meson8b-ddr-clkc" },
138	{ /* sentinel */ }
139};
140
141static struct platform_driver meson8_ddr_clkc_driver = {
142	.probe		= meson8_ddr_clkc_probe,
143	.driver		= {
144		.name	= "meson8-ddr-clkc",
145		.of_match_table = meson8_ddr_clkc_match_table,
146	},
147};
148
149builtin_platform_driver(meson8_ddr_clkc_driver);
150