Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Freescale SAI BCLK as a generic clock driver
4 *
5 * Copyright 2020 Michael Walle <michael@walle.cc>
6 */
7
8#include <linux/clk-provider.h>
9#include <linux/clk.h>
10#include <linux/err.h>
11#include <linux/module.h>
12#include <linux/of.h>
13#include <linux/of_address.h>
14#include <linux/platform_device.h>
15#include <linux/slab.h>
16
17#define I2S_CSR 0x00
18#define I2S_CR2 0x08
19#define I2S_MCR 0x100
20#define CSR_BCE_BIT 28
21#define CSR_TE_BIT 31
22#define CR2_BCD BIT(24)
23#define CR2_DIV_SHIFT 0
24#define CR2_DIV_WIDTH 8
25#define MCR_MOE BIT(30)
26
27struct fsl_sai_data {
28 unsigned int offset; /* Register offset */
29 bool have_mclk; /* Have MCLK control */
30};
31
32struct fsl_sai_clk {
33 const struct fsl_sai_data *data;
34 struct clk_divider bclk_div;
35 struct clk_divider mclk_div;
36 struct clk_gate bclk_gate;
37 struct clk_gate mclk_gate;
38 struct clk_hw *bclk_hw;
39 struct clk_hw *mclk_hw;
40 spinlock_t lock;
41};
42
43static struct clk_hw *
44fsl_sai_of_clk_get(struct of_phandle_args *clkspec, void *data)
45{
46 struct fsl_sai_clk *sai_clk = data;
47
48 if (clkspec->args_count == 0)
49 return sai_clk->bclk_hw;
50
51 if (clkspec->args_count == 1) {
52 if (clkspec->args[0] == 0)
53 return sai_clk->bclk_hw;
54 if (sai_clk->data->have_mclk && clkspec->args[0] == 1)
55 return sai_clk->mclk_hw;
56 }
57
58 return ERR_PTR(-EINVAL);
59}
60
61static int fsl_sai_clk_register(struct device *dev, void __iomem *base,
62 spinlock_t *lock, struct clk_divider *div,
63 struct clk_gate *gate, struct clk_hw **hw,
64 const int gate_bit, const int dir_bit,
65 const int div_reg, char *name)
66{
67 const struct fsl_sai_data *data = device_get_match_data(dev);
68 struct clk_parent_data pdata = { .index = 0 };
69 struct clk_hw *chw;
70 char *cname;
71
72 gate->reg = base + data->offset + I2S_CSR;
73 gate->bit_idx = gate_bit;
74 gate->lock = lock;
75
76 div->reg = base + div_reg;
77 div->shift = CR2_DIV_SHIFT;
78 div->width = CR2_DIV_WIDTH;
79 div->lock = lock;
80
81 cname = devm_kasprintf(dev, GFP_KERNEL, "%s.%s",
82 of_node_full_name(dev->of_node), name);
83 if (!cname)
84 return -ENOMEM;
85
86 /* Set clock direction */
87 writel(dir_bit, base + div_reg);
88
89 chw = devm_clk_hw_register_composite_pdata(dev, cname,
90 &pdata, 1, NULL, NULL,
91 &div->hw,
92 &clk_divider_ops,
93 &gate->hw,
94 &clk_gate_ops,
95 CLK_SET_RATE_GATE);
96 if (IS_ERR(chw))
97 return PTR_ERR(chw);
98
99 *hw = chw;
100
101 return 0;
102}
103
104static int fsl_sai_clk_probe(struct platform_device *pdev)
105{
106 struct device *dev = &pdev->dev;
107 const struct fsl_sai_data *data = device_get_match_data(dev);
108 struct fsl_sai_clk *sai_clk;
109 struct clk *clk_bus;
110 void __iomem *base;
111 int ret;
112
113 sai_clk = devm_kzalloc(dev, sizeof(*sai_clk), GFP_KERNEL);
114 if (!sai_clk)
115 return -ENOMEM;
116
117 base = devm_platform_ioremap_resource(pdev, 0);
118 if (IS_ERR(base))
119 return PTR_ERR(base);
120
121 clk_bus = devm_clk_get_optional_enabled(dev, "bus");
122 if (IS_ERR(clk_bus))
123 return PTR_ERR(clk_bus);
124
125 sai_clk->data = data;
126 spin_lock_init(&sai_clk->lock);
127
128 ret = fsl_sai_clk_register(dev, base, &sai_clk->lock,
129 &sai_clk->bclk_div, &sai_clk->bclk_gate,
130 &sai_clk->bclk_hw, CSR_BCE_BIT, CR2_BCD,
131 data->offset + I2S_CR2, "BCLK");
132 if (ret)
133 return ret;
134
135 if (data->have_mclk) {
136 ret = fsl_sai_clk_register(dev, base, &sai_clk->lock,
137 &sai_clk->mclk_div,
138 &sai_clk->mclk_gate,
139 &sai_clk->mclk_hw,
140 CSR_TE_BIT, MCR_MOE, I2S_MCR,
141 "MCLK");
142 if (ret)
143 return ret;
144 }
145
146 return devm_of_clk_add_hw_provider(dev, fsl_sai_of_clk_get, sai_clk);
147}
148
149static const struct fsl_sai_data fsl_sai_vf610_data = {
150 .offset = 0,
151 .have_mclk = false,
152};
153
154static const struct fsl_sai_data fsl_sai_imx8mq_data = {
155 .offset = 8,
156 .have_mclk = true,
157};
158
159static const struct of_device_id of_fsl_sai_clk_ids[] = {
160 { .compatible = "fsl,vf610-sai-clock", .data = &fsl_sai_vf610_data },
161 { .compatible = "fsl,imx8mq-sai-clock", .data = &fsl_sai_imx8mq_data },
162 { }
163};
164MODULE_DEVICE_TABLE(of, of_fsl_sai_clk_ids);
165
166static struct platform_driver fsl_sai_clk_driver = {
167 .probe = fsl_sai_clk_probe,
168 .driver = {
169 .name = "fsl-sai-clk",
170 .of_match_table = of_fsl_sai_clk_ids,
171 },
172};
173module_platform_driver(fsl_sai_clk_driver);
174
175MODULE_DESCRIPTION("Freescale SAI bitclock-as-a-clock driver");
176MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
177MODULE_ALIAS("platform:fsl-sai-clk");