1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2018-2019, Intel Corporation. |
4 | * Copyright (C) 2012 Freescale Semiconductor, Inc. |
5 | * Copyright (C) 2012 Linaro Ltd. |
6 | * |
7 | * Based on syscon driver. |
8 | */ |
9 | |
10 | #include <linux/arm-smccc.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <linux/mfd/altera-sysmgr.h> |
14 | #include <linux/mfd/syscon.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/regmap.h> |
19 | #include <linux/slab.h> |
20 | |
21 | /** |
22 | * struct altr_sysmgr - Altera SOCFPGA System Manager |
23 | * @regmap: the regmap used for System Manager accesses. |
24 | */ |
25 | struct altr_sysmgr { |
26 | struct regmap *regmap; |
27 | }; |
28 | |
29 | static struct platform_driver altr_sysmgr_driver; |
30 | |
31 | /** |
32 | * s10_protected_reg_write |
33 | * Write to a protected SMC register. |
34 | * @base: Base address of System Manager |
35 | * @reg: Address offset of register |
36 | * @val: Value to write |
37 | * Return: INTEL_SIP_SMC_STATUS_OK (0) on success |
38 | * INTEL_SIP_SMC_REG_ERROR on error |
39 | * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION if not supported |
40 | */ |
41 | static int s10_protected_reg_write(void *base, |
42 | unsigned int reg, unsigned int val) |
43 | { |
44 | struct arm_smccc_res result; |
45 | unsigned long sysmgr_base = (unsigned long)base; |
46 | |
47 | arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE, sysmgr_base + reg, |
48 | val, 0, 0, 0, 0, 0, &result); |
49 | |
50 | return (int)result.a0; |
51 | } |
52 | |
53 | /** |
54 | * s10_protected_reg_read |
55 | * Read the status of a protected SMC register |
56 | * @base: Base address of System Manager. |
57 | * @reg: Address of register |
58 | * @val: Value read. |
59 | * Return: INTEL_SIP_SMC_STATUS_OK (0) on success |
60 | * INTEL_SIP_SMC_REG_ERROR on error |
61 | * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION if not supported |
62 | */ |
63 | static int s10_protected_reg_read(void *base, |
64 | unsigned int reg, unsigned int *val) |
65 | { |
66 | struct arm_smccc_res result; |
67 | unsigned long sysmgr_base = (unsigned long)base; |
68 | |
69 | arm_smccc_smc(INTEL_SIP_SMC_REG_READ, sysmgr_base + reg, |
70 | 0, 0, 0, 0, 0, 0, &result); |
71 | |
72 | *val = (unsigned int)result.a1; |
73 | |
74 | return (int)result.a0; |
75 | } |
76 | |
77 | static struct regmap_config altr_sysmgr_regmap_cfg = { |
78 | .name = "altr_sysmgr" , |
79 | .reg_bits = 32, |
80 | .reg_stride = 4, |
81 | .val_bits = 32, |
82 | .fast_io = true, |
83 | .use_single_read = true, |
84 | .use_single_write = true, |
85 | }; |
86 | |
87 | /** |
88 | * altr_sysmgr_regmap_lookup_by_phandle |
89 | * Find the sysmgr previous configured in probe() and return regmap property. |
90 | * Return: regmap if found or error if not found. |
91 | * |
92 | * @np: Pointer to device's Device Tree node |
93 | * @property: Device Tree property name which references the sysmgr |
94 | */ |
95 | struct regmap *altr_sysmgr_regmap_lookup_by_phandle(struct device_node *np, |
96 | const char *property) |
97 | { |
98 | struct device *dev; |
99 | struct altr_sysmgr *sysmgr; |
100 | struct device_node *sysmgr_np; |
101 | |
102 | if (property) |
103 | sysmgr_np = of_parse_phandle(np, phandle_name: property, index: 0); |
104 | else |
105 | sysmgr_np = np; |
106 | |
107 | if (!sysmgr_np) |
108 | return ERR_PTR(error: -ENODEV); |
109 | |
110 | dev = driver_find_device_by_of_node(drv: &altr_sysmgr_driver.driver, |
111 | np: (void *)sysmgr_np); |
112 | if (property) |
113 | of_node_put(node: sysmgr_np); |
114 | |
115 | if (!dev) |
116 | return ERR_PTR(error: -EPROBE_DEFER); |
117 | |
118 | sysmgr = dev_get_drvdata(dev); |
119 | |
120 | return sysmgr->regmap; |
121 | } |
122 | EXPORT_SYMBOL_GPL(altr_sysmgr_regmap_lookup_by_phandle); |
123 | |
124 | static int sysmgr_probe(struct platform_device *pdev) |
125 | { |
126 | struct altr_sysmgr *sysmgr; |
127 | struct regmap *regmap; |
128 | struct resource *res; |
129 | struct regmap_config sysmgr_config = altr_sysmgr_regmap_cfg; |
130 | struct device *dev = &pdev->dev; |
131 | struct device_node *np = dev->of_node; |
132 | void __iomem *base; |
133 | |
134 | sysmgr = devm_kzalloc(dev, size: sizeof(*sysmgr), GFP_KERNEL); |
135 | if (!sysmgr) |
136 | return -ENOMEM; |
137 | |
138 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
139 | if (!res) |
140 | return -ENOENT; |
141 | |
142 | sysmgr_config.max_register = resource_size(res) - |
143 | sysmgr_config.reg_stride; |
144 | if (of_device_is_compatible(device: np, "altr,sys-mgr-s10" )) { |
145 | sysmgr_config.reg_read = s10_protected_reg_read; |
146 | sysmgr_config.reg_write = s10_protected_reg_write; |
147 | |
148 | /* Need physical address for SMCC call */ |
149 | regmap = devm_regmap_init(dev, NULL, |
150 | (void *)(uintptr_t)res->start, |
151 | &sysmgr_config); |
152 | } else { |
153 | base = devm_ioremap(dev, offset: res->start, size: resource_size(res)); |
154 | if (!base) |
155 | return -ENOMEM; |
156 | |
157 | sysmgr_config.max_register = resource_size(res) - 4; |
158 | regmap = devm_regmap_init_mmio(dev, base, &sysmgr_config); |
159 | } |
160 | |
161 | if (IS_ERR(ptr: regmap)) { |
162 | pr_err("regmap init failed\n" ); |
163 | return PTR_ERR(ptr: regmap); |
164 | } |
165 | |
166 | sysmgr->regmap = regmap; |
167 | |
168 | platform_set_drvdata(pdev, data: sysmgr); |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static const struct of_device_id altr_sysmgr_of_match[] = { |
174 | { .compatible = "altr,sys-mgr" }, |
175 | { .compatible = "altr,sys-mgr-s10" }, |
176 | {}, |
177 | }; |
178 | MODULE_DEVICE_TABLE(of, altr_sysmgr_of_match); |
179 | |
180 | static struct platform_driver altr_sysmgr_driver = { |
181 | .probe = sysmgr_probe, |
182 | .driver = { |
183 | .name = "altr,system_manager" , |
184 | .of_match_table = altr_sysmgr_of_match, |
185 | }, |
186 | }; |
187 | |
188 | static int __init altr_sysmgr_init(void) |
189 | { |
190 | return platform_driver_register(&altr_sysmgr_driver); |
191 | } |
192 | core_initcall(altr_sysmgr_init); |
193 | |
194 | static void __exit altr_sysmgr_exit(void) |
195 | { |
196 | platform_driver_unregister(&altr_sysmgr_driver); |
197 | } |
198 | module_exit(altr_sysmgr_exit); |
199 | |
200 | MODULE_AUTHOR("Thor Thayer <>" ); |
201 | MODULE_DESCRIPTION("SOCFPGA System Manager driver" ); |
202 | |