1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2014, The Linux foundation. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/err.h> |
8 | #include <linux/io.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/of_platform.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/mfd/syscon.h> |
15 | #include <dt-bindings/soc/qcom,gsbi.h> |
16 | |
17 | #define GSBI_CTRL_REG 0x0000 |
18 | #define GSBI_PROTOCOL_SHIFT 4 |
19 | #define MAX_GSBI 12 |
20 | |
21 | #define TCSR_ADM_CRCI_BASE 0x70 |
22 | |
23 | struct crci_config { |
24 | u32 num_rows; |
25 | const u32 (*array)[MAX_GSBI]; |
26 | }; |
27 | |
28 | static const u32 crci_ipq8064[][MAX_GSBI] = { |
29 | { |
30 | 0x000003, 0x00000c, 0x000030, 0x0000c0, |
31 | 0x000300, 0x000c00, 0x003000, 0x00c000, |
32 | 0x030000, 0x0c0000, 0x300000, 0xc00000 |
33 | }, |
34 | { |
35 | 0x000003, 0x00000c, 0x000030, 0x0000c0, |
36 | 0x000300, 0x000c00, 0x003000, 0x00c000, |
37 | 0x030000, 0x0c0000, 0x300000, 0xc00000 |
38 | }, |
39 | }; |
40 | |
41 | static const struct crci_config config_ipq8064 = { |
42 | .num_rows = ARRAY_SIZE(crci_ipq8064), |
43 | .array = crci_ipq8064, |
44 | }; |
45 | |
46 | static const unsigned int crci_apq8064[][MAX_GSBI] = { |
47 | { |
48 | 0x001800, 0x006000, 0x000030, 0x0000c0, |
49 | 0x000300, 0x000400, 0x000000, 0x000000, |
50 | 0x000000, 0x000000, 0x000000, 0x000000 |
51 | }, |
52 | { |
53 | 0x000000, 0x000000, 0x000000, 0x000000, |
54 | 0x000000, 0x000020, 0x0000c0, 0x000000, |
55 | 0x000000, 0x000000, 0x000000, 0x000000 |
56 | }, |
57 | }; |
58 | |
59 | static const struct crci_config config_apq8064 = { |
60 | .num_rows = ARRAY_SIZE(crci_apq8064), |
61 | .array = crci_apq8064, |
62 | }; |
63 | |
64 | static const unsigned int crci_msm8960[][MAX_GSBI] = { |
65 | { |
66 | 0x000003, 0x00000c, 0x000030, 0x0000c0, |
67 | 0x000300, 0x000400, 0x000000, 0x000000, |
68 | 0x000000, 0x000000, 0x000000, 0x000000 |
69 | }, |
70 | { |
71 | 0x000000, 0x000000, 0x000000, 0x000000, |
72 | 0x000000, 0x000020, 0x0000c0, 0x000300, |
73 | 0x001800, 0x006000, 0x000000, 0x000000 |
74 | }, |
75 | }; |
76 | |
77 | static const struct crci_config config_msm8960 = { |
78 | .num_rows = ARRAY_SIZE(crci_msm8960), |
79 | .array = crci_msm8960, |
80 | }; |
81 | |
82 | static const unsigned int crci_msm8660[][MAX_GSBI] = { |
83 | { /* ADM 0 - B */ |
84 | 0x000003, 0x00000c, 0x000030, 0x0000c0, |
85 | 0x000300, 0x000c00, 0x003000, 0x00c000, |
86 | 0x030000, 0x0c0000, 0x300000, 0xc00000 |
87 | }, |
88 | { /* ADM 0 - B */ |
89 | 0x000003, 0x00000c, 0x000030, 0x0000c0, |
90 | 0x000300, 0x000c00, 0x003000, 0x00c000, |
91 | 0x030000, 0x0c0000, 0x300000, 0xc00000 |
92 | }, |
93 | { /* ADM 1 - A */ |
94 | 0x000003, 0x00000c, 0x000030, 0x0000c0, |
95 | 0x000300, 0x000c00, 0x003000, 0x00c000, |
96 | 0x030000, 0x0c0000, 0x300000, 0xc00000 |
97 | }, |
98 | { /* ADM 1 - B */ |
99 | 0x000003, 0x00000c, 0x000030, 0x0000c0, |
100 | 0x000300, 0x000c00, 0x003000, 0x00c000, |
101 | 0x030000, 0x0c0000, 0x300000, 0xc00000 |
102 | }, |
103 | }; |
104 | |
105 | static const struct crci_config config_msm8660 = { |
106 | .num_rows = ARRAY_SIZE(crci_msm8660), |
107 | .array = crci_msm8660, |
108 | }; |
109 | |
110 | struct gsbi_info { |
111 | struct clk *hclk; |
112 | u32 mode; |
113 | u32 crci; |
114 | struct regmap *tcsr; |
115 | }; |
116 | |
117 | static const struct of_device_id tcsr_dt_match[] __maybe_unused = { |
118 | { .compatible = "qcom,tcsr-ipq8064" , .data = &config_ipq8064}, |
119 | { .compatible = "qcom,tcsr-apq8064" , .data = &config_apq8064}, |
120 | { .compatible = "qcom,tcsr-msm8960" , .data = &config_msm8960}, |
121 | { .compatible = "qcom,tcsr-msm8660" , .data = &config_msm8660}, |
122 | { }, |
123 | }; |
124 | |
125 | static int gsbi_probe(struct platform_device *pdev) |
126 | { |
127 | struct device_node *node = pdev->dev.of_node; |
128 | struct device_node *tcsr_node; |
129 | const struct of_device_id *match; |
130 | void __iomem *base; |
131 | struct gsbi_info *gsbi; |
132 | int i; |
133 | u32 mask, gsbi_num; |
134 | const struct crci_config *config = NULL; |
135 | |
136 | gsbi = devm_kzalloc(dev: &pdev->dev, size: sizeof(*gsbi), GFP_KERNEL); |
137 | |
138 | if (!gsbi) |
139 | return -ENOMEM; |
140 | |
141 | base = devm_platform_ioremap_resource(pdev, index: 0); |
142 | if (IS_ERR(ptr: base)) |
143 | return PTR_ERR(ptr: base); |
144 | |
145 | /* get the tcsr node and setup the config and regmap */ |
146 | gsbi->tcsr = syscon_regmap_lookup_by_phandle(np: node, property: "syscon-tcsr" ); |
147 | |
148 | if (!IS_ERR(ptr: gsbi->tcsr)) { |
149 | tcsr_node = of_parse_phandle(np: node, phandle_name: "syscon-tcsr" , index: 0); |
150 | if (tcsr_node) { |
151 | match = of_match_node(matches: tcsr_dt_match, node: tcsr_node); |
152 | if (match) |
153 | config = match->data; |
154 | else |
155 | dev_warn(&pdev->dev, "no matching TCSR\n" ); |
156 | |
157 | of_node_put(node: tcsr_node); |
158 | } |
159 | } |
160 | |
161 | if (of_property_read_u32(np: node, propname: "cell-index" , out_value: &gsbi_num)) { |
162 | dev_err(&pdev->dev, "missing cell-index\n" ); |
163 | return -EINVAL; |
164 | } |
165 | |
166 | if (gsbi_num < 1 || gsbi_num > MAX_GSBI) { |
167 | dev_err(&pdev->dev, "invalid cell-index\n" ); |
168 | return -EINVAL; |
169 | } |
170 | |
171 | if (of_property_read_u32(np: node, propname: "qcom,mode" , out_value: &gsbi->mode)) { |
172 | dev_err(&pdev->dev, "missing mode configuration\n" ); |
173 | return -EINVAL; |
174 | } |
175 | |
176 | /* not required, so default to 0 if not present */ |
177 | of_property_read_u32(np: node, propname: "qcom,crci" , out_value: &gsbi->crci); |
178 | |
179 | dev_info(&pdev->dev, "GSBI port protocol: %d crci: %d\n" , |
180 | gsbi->mode, gsbi->crci); |
181 | gsbi->hclk = devm_clk_get_enabled(dev: &pdev->dev, id: "iface" ); |
182 | if (IS_ERR(ptr: gsbi->hclk)) |
183 | return PTR_ERR(ptr: gsbi->hclk); |
184 | |
185 | writel_relaxed((gsbi->mode << GSBI_PROTOCOL_SHIFT) | gsbi->crci, |
186 | base + GSBI_CTRL_REG); |
187 | |
188 | /* |
189 | * modify tcsr to reflect mode and ADM CRCI mux |
190 | * Each gsbi contains a pair of bits, one for RX and one for TX |
191 | * SPI mode requires both bits cleared, otherwise they are set |
192 | */ |
193 | if (config) { |
194 | for (i = 0; i < config->num_rows; i++) { |
195 | mask = config->array[i][gsbi_num - 1]; |
196 | |
197 | if (gsbi->mode == GSBI_PROT_SPI) |
198 | regmap_update_bits(map: gsbi->tcsr, |
199 | TCSR_ADM_CRCI_BASE + 4 * i, mask, val: 0); |
200 | else |
201 | regmap_update_bits(map: gsbi->tcsr, |
202 | TCSR_ADM_CRCI_BASE + 4 * i, mask, val: mask); |
203 | |
204 | } |
205 | } |
206 | |
207 | /* make sure the gsbi control write is not reordered */ |
208 | wmb(); |
209 | |
210 | platform_set_drvdata(pdev, data: gsbi); |
211 | |
212 | return of_platform_populate(root: node, NULL, NULL, parent: &pdev->dev); |
213 | } |
214 | |
215 | static void gsbi_remove(struct platform_device *pdev) |
216 | { |
217 | struct gsbi_info *gsbi = platform_get_drvdata(pdev); |
218 | |
219 | clk_disable_unprepare(clk: gsbi->hclk); |
220 | } |
221 | |
222 | static const struct of_device_id gsbi_dt_match[] = { |
223 | { .compatible = "qcom,gsbi-v1.0.0" , }, |
224 | { }, |
225 | }; |
226 | |
227 | MODULE_DEVICE_TABLE(of, gsbi_dt_match); |
228 | |
229 | static struct platform_driver gsbi_driver = { |
230 | .driver = { |
231 | .name = "gsbi" , |
232 | .of_match_table = gsbi_dt_match, |
233 | }, |
234 | .probe = gsbi_probe, |
235 | .remove_new = gsbi_remove, |
236 | }; |
237 | |
238 | module_platform_driver(gsbi_driver); |
239 | |
240 | MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>" ); |
241 | MODULE_DESCRIPTION("QCOM GSBI driver" ); |
242 | MODULE_LICENSE("GPL v2" ); |
243 | |