1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Copyright (c) 2015 Samsung Electronics Co., Ltd. |
4 | // http://www.samsung.com/ |
5 | // |
6 | // Exynos - SROM Controller support |
7 | // Author: Pankaj Dubey <pankaj.dubey@samsung.com> |
8 | |
9 | #include <linux/io.h> |
10 | #include <linux/init.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_address.h> |
13 | #include <linux/of_platform.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/slab.h> |
16 | |
17 | #include "exynos-srom.h" |
18 | |
19 | static const unsigned long exynos_srom_offsets[] = { |
20 | /* SROM side */ |
21 | EXYNOS_SROM_BW, |
22 | EXYNOS_SROM_BC0, |
23 | EXYNOS_SROM_BC1, |
24 | EXYNOS_SROM_BC2, |
25 | EXYNOS_SROM_BC3, |
26 | }; |
27 | |
28 | /** |
29 | * struct exynos_srom_reg_dump: register dump of SROM Controller registers. |
30 | * @offset: srom register offset from the controller base address. |
31 | * @value: the value of register under the offset. |
32 | */ |
33 | struct exynos_srom_reg_dump { |
34 | u32 offset; |
35 | u32 value; |
36 | }; |
37 | |
38 | /** |
39 | * struct exynos_srom: platform data for exynos srom controller driver. |
40 | * @dev: platform device pointer |
41 | * @reg_base: srom base address |
42 | * @reg_offset: exynos_srom_reg_dump pointer to hold offset and its value. |
43 | */ |
44 | struct exynos_srom { |
45 | struct device *dev; |
46 | void __iomem *reg_base; |
47 | struct exynos_srom_reg_dump *reg_offset; |
48 | }; |
49 | |
50 | static struct exynos_srom_reg_dump * |
51 | exynos_srom_alloc_reg_dump(const unsigned long *rdump, |
52 | unsigned long nr_rdump) |
53 | { |
54 | struct exynos_srom_reg_dump *rd; |
55 | unsigned int i; |
56 | |
57 | rd = kcalloc(n: nr_rdump, size: sizeof(*rd), GFP_KERNEL); |
58 | if (!rd) |
59 | return NULL; |
60 | |
61 | for (i = 0; i < nr_rdump; ++i) |
62 | rd[i].offset = rdump[i]; |
63 | |
64 | return rd; |
65 | } |
66 | |
67 | static int exynos_srom_configure_bank(struct exynos_srom *srom, |
68 | struct device_node *np) |
69 | { |
70 | u32 bank, width, pmc = 0; |
71 | u32 timing[6]; |
72 | u32 cs, bw; |
73 | |
74 | if (of_property_read_u32(np, propname: "reg" , out_value: &bank)) |
75 | return -EINVAL; |
76 | if (of_property_read_u32(np, propname: "reg-io-width" , out_value: &width)) |
77 | width = 1; |
78 | if (of_property_read_bool(np, propname: "samsung,srom-page-mode" )) |
79 | pmc = 1 << EXYNOS_SROM_BCX__PMC__SHIFT; |
80 | if (of_property_read_u32_array(np, propname: "samsung,srom-timing" , out_values: timing, |
81 | ARRAY_SIZE(timing))) |
82 | return -EINVAL; |
83 | |
84 | bank *= 4; /* Convert bank into shift/offset */ |
85 | |
86 | cs = 1 << EXYNOS_SROM_BW__BYTEENABLE__SHIFT; |
87 | if (width == 2) |
88 | cs |= 1 << EXYNOS_SROM_BW__DATAWIDTH__SHIFT; |
89 | |
90 | bw = readl_relaxed(srom->reg_base + EXYNOS_SROM_BW); |
91 | bw = (bw & ~(EXYNOS_SROM_BW__CS_MASK << bank)) | (cs << bank); |
92 | writel_relaxed(bw, srom->reg_base + EXYNOS_SROM_BW); |
93 | |
94 | writel_relaxed(pmc | (timing[0] << EXYNOS_SROM_BCX__TACP__SHIFT) | |
95 | (timing[1] << EXYNOS_SROM_BCX__TCAH__SHIFT) | |
96 | (timing[2] << EXYNOS_SROM_BCX__TCOH__SHIFT) | |
97 | (timing[3] << EXYNOS_SROM_BCX__TACC__SHIFT) | |
98 | (timing[4] << EXYNOS_SROM_BCX__TCOS__SHIFT) | |
99 | (timing[5] << EXYNOS_SROM_BCX__TACS__SHIFT), |
100 | srom->reg_base + EXYNOS_SROM_BC0 + bank); |
101 | |
102 | return 0; |
103 | } |
104 | |
105 | static int exynos_srom_probe(struct platform_device *pdev) |
106 | { |
107 | struct device_node *np, *child; |
108 | struct exynos_srom *srom; |
109 | struct device *dev = &pdev->dev; |
110 | bool bad_bank_config = false; |
111 | |
112 | np = dev->of_node; |
113 | if (!np) { |
114 | dev_err(&pdev->dev, "could not find device info\n" ); |
115 | return -EINVAL; |
116 | } |
117 | |
118 | srom = devm_kzalloc(dev: &pdev->dev, |
119 | size: sizeof(struct exynos_srom), GFP_KERNEL); |
120 | if (!srom) |
121 | return -ENOMEM; |
122 | |
123 | srom->dev = dev; |
124 | srom->reg_base = of_iomap(node: np, index: 0); |
125 | if (!srom->reg_base) { |
126 | dev_err(&pdev->dev, "iomap of exynos srom controller failed\n" ); |
127 | return -ENOMEM; |
128 | } |
129 | |
130 | platform_set_drvdata(pdev, data: srom); |
131 | |
132 | srom->reg_offset = exynos_srom_alloc_reg_dump(rdump: exynos_srom_offsets, |
133 | ARRAY_SIZE(exynos_srom_offsets)); |
134 | if (!srom->reg_offset) { |
135 | iounmap(addr: srom->reg_base); |
136 | return -ENOMEM; |
137 | } |
138 | |
139 | for_each_child_of_node(np, child) { |
140 | if (exynos_srom_configure_bank(srom, np: child)) { |
141 | dev_err(dev, |
142 | "Could not decode bank configuration for %pOFn\n" , |
143 | child); |
144 | bad_bank_config = true; |
145 | } |
146 | } |
147 | |
148 | /* |
149 | * If any bank failed to configure, we still provide suspend/resume, |
150 | * but do not probe child devices |
151 | */ |
152 | if (bad_bank_config) |
153 | return 0; |
154 | |
155 | return of_platform_populate(root: np, NULL, NULL, parent: dev); |
156 | } |
157 | |
158 | #ifdef CONFIG_PM_SLEEP |
159 | static void exynos_srom_save(void __iomem *base, |
160 | struct exynos_srom_reg_dump *rd, |
161 | unsigned int num_regs) |
162 | { |
163 | for (; num_regs > 0; --num_regs, ++rd) |
164 | rd->value = readl(addr: base + rd->offset); |
165 | } |
166 | |
167 | static void exynos_srom_restore(void __iomem *base, |
168 | const struct exynos_srom_reg_dump *rd, |
169 | unsigned int num_regs) |
170 | { |
171 | for (; num_regs > 0; --num_regs, ++rd) |
172 | writel(val: rd->value, addr: base + rd->offset); |
173 | } |
174 | |
175 | static int exynos_srom_suspend(struct device *dev) |
176 | { |
177 | struct exynos_srom *srom = dev_get_drvdata(dev); |
178 | |
179 | exynos_srom_save(base: srom->reg_base, rd: srom->reg_offset, |
180 | ARRAY_SIZE(exynos_srom_offsets)); |
181 | return 0; |
182 | } |
183 | |
184 | static int exynos_srom_resume(struct device *dev) |
185 | { |
186 | struct exynos_srom *srom = dev_get_drvdata(dev); |
187 | |
188 | exynos_srom_restore(base: srom->reg_base, rd: srom->reg_offset, |
189 | ARRAY_SIZE(exynos_srom_offsets)); |
190 | return 0; |
191 | } |
192 | #endif |
193 | |
194 | static const struct of_device_id of_exynos_srom_ids[] = { |
195 | { |
196 | .compatible = "samsung,exynos4210-srom" , |
197 | }, |
198 | {}, |
199 | }; |
200 | |
201 | static SIMPLE_DEV_PM_OPS(exynos_srom_pm_ops, exynos_srom_suspend, exynos_srom_resume); |
202 | |
203 | static struct platform_driver exynos_srom_driver = { |
204 | .probe = exynos_srom_probe, |
205 | .driver = { |
206 | .name = "exynos-srom" , |
207 | .of_match_table = of_exynos_srom_ids, |
208 | .pm = &exynos_srom_pm_ops, |
209 | .suppress_bind_attrs = true, |
210 | }, |
211 | }; |
212 | builtin_platform_driver(exynos_srom_driver); |
213 | |