1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * MEN Chameleon Bus. |
4 | * |
5 | * Copyright (C) 2014 MEN Mikroelektronik GmbH (www.men.de) |
6 | * Author: Andreas Werner <andreas.werner@men.de> |
7 | */ |
8 | |
9 | #include <linux/platform_device.h> |
10 | #include <linux/module.h> |
11 | #include <linux/dmi.h> |
12 | #include <linux/mcb.h> |
13 | #include <linux/io.h> |
14 | #include "mcb-internal.h" |
15 | |
16 | struct priv { |
17 | struct mcb_bus *bus; |
18 | struct resource *mem; |
19 | void __iomem *base; |
20 | }; |
21 | |
22 | static int mcb_lpc_probe(struct platform_device *pdev) |
23 | { |
24 | struct resource *res; |
25 | struct priv *priv; |
26 | int ret = 0, table_size; |
27 | |
28 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
29 | if (!priv) |
30 | return -ENOMEM; |
31 | |
32 | priv->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
33 | if (!priv->mem) { |
34 | dev_err(&pdev->dev, "No Memory resource\n" ); |
35 | return -ENODEV; |
36 | } |
37 | |
38 | res = devm_request_mem_region(&pdev->dev, priv->mem->start, |
39 | resource_size(priv->mem), |
40 | KBUILD_MODNAME); |
41 | if (!res) { |
42 | dev_err(&pdev->dev, "Failed to request IO memory\n" ); |
43 | return -EBUSY; |
44 | } |
45 | |
46 | priv->base = devm_ioremap(dev: &pdev->dev, offset: priv->mem->start, |
47 | size: resource_size(res: priv->mem)); |
48 | if (!priv->base) { |
49 | dev_err(&pdev->dev, "Cannot ioremap\n" ); |
50 | return -ENOMEM; |
51 | } |
52 | |
53 | platform_set_drvdata(pdev, data: priv); |
54 | |
55 | priv->bus = mcb_alloc_bus(carrier: &pdev->dev); |
56 | if (IS_ERR(ptr: priv->bus)) |
57 | return PTR_ERR(ptr: priv->bus); |
58 | |
59 | ret = chameleon_parse_cells(bus: priv->bus, mapbase: priv->mem->start, base: priv->base); |
60 | if (ret < 0) { |
61 | goto out_mcb_bus; |
62 | } |
63 | |
64 | table_size = ret; |
65 | |
66 | if (table_size < CHAM_HEADER_SIZE) { |
67 | /* Release the previous resources */ |
68 | devm_iounmap(dev: &pdev->dev, addr: priv->base); |
69 | devm_release_mem_region(&pdev->dev, priv->mem->start, resource_size(priv->mem)); |
70 | |
71 | /* Then, allocate it again with the actual chameleon table size */ |
72 | res = devm_request_mem_region(&pdev->dev, priv->mem->start, |
73 | table_size, |
74 | KBUILD_MODNAME); |
75 | if (!res) { |
76 | dev_err(&pdev->dev, "Failed to request PCI memory\n" ); |
77 | ret = -EBUSY; |
78 | goto out_mcb_bus; |
79 | } |
80 | |
81 | priv->base = devm_ioremap(dev: &pdev->dev, offset: priv->mem->start, size: table_size); |
82 | if (!priv->base) { |
83 | dev_err(&pdev->dev, "Cannot ioremap\n" ); |
84 | ret = -ENOMEM; |
85 | goto out_mcb_bus; |
86 | } |
87 | |
88 | platform_set_drvdata(pdev, data: priv); |
89 | } |
90 | |
91 | mcb_bus_add_devices(bus: priv->bus); |
92 | |
93 | return 0; |
94 | |
95 | out_mcb_bus: |
96 | mcb_release_bus(bus: priv->bus); |
97 | return ret; |
98 | } |
99 | |
100 | static int mcb_lpc_remove(struct platform_device *pdev) |
101 | { |
102 | struct priv *priv = platform_get_drvdata(pdev); |
103 | |
104 | mcb_release_bus(bus: priv->bus); |
105 | |
106 | return 0; |
107 | } |
108 | |
109 | static struct platform_device *mcb_lpc_pdev; |
110 | |
111 | static int mcb_lpc_create_platform_device(const struct dmi_system_id *id) |
112 | { |
113 | struct resource *res = id->driver_data; |
114 | int ret; |
115 | |
116 | mcb_lpc_pdev = platform_device_alloc(name: "mcb-lpc" , id: -1); |
117 | if (!mcb_lpc_pdev) |
118 | return -ENOMEM; |
119 | |
120 | ret = platform_device_add_resources(pdev: mcb_lpc_pdev, res, num: 1); |
121 | if (ret) |
122 | goto out_put; |
123 | |
124 | ret = platform_device_add(pdev: mcb_lpc_pdev); |
125 | if (ret) |
126 | goto out_put; |
127 | |
128 | return 0; |
129 | |
130 | out_put: |
131 | platform_device_put(pdev: mcb_lpc_pdev); |
132 | return ret; |
133 | } |
134 | |
135 | static struct resource sc24_fpga_resource = DEFINE_RES_MEM(0xe000e000, CHAM_HEADER_SIZE); |
136 | static struct resource sc31_fpga_resource = DEFINE_RES_MEM(0xf000e000, CHAM_HEADER_SIZE); |
137 | |
138 | static struct platform_driver mcb_lpc_driver = { |
139 | .driver = { |
140 | .name = "mcb-lpc" , |
141 | }, |
142 | .probe = mcb_lpc_probe, |
143 | .remove = mcb_lpc_remove, |
144 | }; |
145 | |
146 | static const struct dmi_system_id mcb_lpc_dmi_table[] = { |
147 | { |
148 | .ident = "SC24" , |
149 | .matches = { |
150 | DMI_MATCH(DMI_SYS_VENDOR, "MEN" ), |
151 | DMI_MATCH(DMI_PRODUCT_VERSION, "14SC24" ), |
152 | }, |
153 | .driver_data = (void *)&sc24_fpga_resource, |
154 | .callback = mcb_lpc_create_platform_device, |
155 | }, |
156 | { |
157 | .ident = "SC31" , |
158 | .matches = { |
159 | DMI_MATCH(DMI_SYS_VENDOR, "MEN" ), |
160 | DMI_MATCH(DMI_PRODUCT_VERSION, "14SC31" ), |
161 | }, |
162 | .driver_data = (void *)&sc31_fpga_resource, |
163 | .callback = mcb_lpc_create_platform_device, |
164 | }, |
165 | {} |
166 | }; |
167 | MODULE_DEVICE_TABLE(dmi, mcb_lpc_dmi_table); |
168 | |
169 | static int __init mcb_lpc_init(void) |
170 | { |
171 | if (!dmi_check_system(list: mcb_lpc_dmi_table)) |
172 | return -ENODEV; |
173 | |
174 | return platform_driver_register(&mcb_lpc_driver); |
175 | } |
176 | |
177 | static void __exit mcb_lpc_exit(void) |
178 | { |
179 | platform_device_unregister(mcb_lpc_pdev); |
180 | platform_driver_unregister(&mcb_lpc_driver); |
181 | } |
182 | |
183 | module_init(mcb_lpc_init); |
184 | module_exit(mcb_lpc_exit); |
185 | |
186 | MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>" ); |
187 | MODULE_LICENSE("GPL" ); |
188 | MODULE_DESCRIPTION("MCB over LPC support" ); |
189 | MODULE_IMPORT_NS(MCB); |
190 | |