1d384d6f4SThierry Escande /*
2d384d6f4SThierry Escande  * coreboot_table.c
3d384d6f4SThierry Escande  *
4d384d6f4SThierry Escande  * Module providing coreboot table access.
5d384d6f4SThierry Escande  *
6d384d6f4SThierry Escande  * Copyright 2017 Google Inc.
7570d30c2SSamuel Holland  * Copyright 2017 Samuel Holland <samuel@sholland.org>
8d384d6f4SThierry Escande  *
9d384d6f4SThierry Escande  * This program is free software; you can redistribute it and/or modify
10d384d6f4SThierry Escande  * it under the terms of the GNU General Public License v2.0 as published by
11d384d6f4SThierry Escande  * the Free Software Foundation.
12d384d6f4SThierry Escande  *
13d384d6f4SThierry Escande  * This program is distributed in the hope that it will be useful,
14d384d6f4SThierry Escande  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15d384d6f4SThierry Escande  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16d384d6f4SThierry Escande  * GNU General Public License for more details.
17d384d6f4SThierry Escande  */
18d384d6f4SThierry Escande 
19a28aad66SStephen Boyd #include <linux/acpi.h>
20570d30c2SSamuel Holland #include <linux/device.h>
21d384d6f4SThierry Escande #include <linux/err.h>
22d384d6f4SThierry Escande #include <linux/init.h>
23d384d6f4SThierry Escande #include <linux/io.h>
24d384d6f4SThierry Escande #include <linux/kernel.h>
25d384d6f4SThierry Escande #include <linux/module.h>
26a28aad66SStephen Boyd #include <linux/of.h>
27a28aad66SStephen Boyd #include <linux/platform_device.h>
28570d30c2SSamuel Holland #include <linux/slab.h>
29d384d6f4SThierry Escande 
30d384d6f4SThierry Escande #include "coreboot_table.h"
31d384d6f4SThierry Escande 
32570d30c2SSamuel Holland #define CB_DEV(d) container_of(d, struct coreboot_device, dev)
33570d30c2SSamuel Holland #define CB_DRV(d) container_of(d, struct coreboot_driver, drv)
34d384d6f4SThierry Escande 
35d384d6f4SThierry Escande static struct coreboot_table_header __iomem *ptr_header;
36d384d6f4SThierry Escande 
37570d30c2SSamuel Holland static int coreboot_bus_match(struct device *dev, struct device_driver *drv)
38570d30c2SSamuel Holland {
39570d30c2SSamuel Holland 	struct coreboot_device *device = CB_DEV(dev);
40570d30c2SSamuel Holland 	struct coreboot_driver *driver = CB_DRV(drv);
41570d30c2SSamuel Holland 
42570d30c2SSamuel Holland 	return device->entry.tag == driver->tag;
43570d30c2SSamuel Holland }
44570d30c2SSamuel Holland 
45570d30c2SSamuel Holland static int coreboot_bus_probe(struct device *dev)
46570d30c2SSamuel Holland {
47570d30c2SSamuel Holland 	int ret = -ENODEV;
48570d30c2SSamuel Holland 	struct coreboot_device *device = CB_DEV(dev);
49570d30c2SSamuel Holland 	struct coreboot_driver *driver = CB_DRV(dev->driver);
50570d30c2SSamuel Holland 
51570d30c2SSamuel Holland 	if (driver->probe)
52570d30c2SSamuel Holland 		ret = driver->probe(device);
53570d30c2SSamuel Holland 
54570d30c2SSamuel Holland 	return ret;
55570d30c2SSamuel Holland }
56570d30c2SSamuel Holland 
57570d30c2SSamuel Holland static int coreboot_bus_remove(struct device *dev)
58570d30c2SSamuel Holland {
59570d30c2SSamuel Holland 	int ret = 0;
60570d30c2SSamuel Holland 	struct coreboot_device *device = CB_DEV(dev);
61570d30c2SSamuel Holland 	struct coreboot_driver *driver = CB_DRV(dev->driver);
62570d30c2SSamuel Holland 
63570d30c2SSamuel Holland 	if (driver->remove)
64570d30c2SSamuel Holland 		ret = driver->remove(device);
65570d30c2SSamuel Holland 
66570d30c2SSamuel Holland 	return ret;
67570d30c2SSamuel Holland }
68570d30c2SSamuel Holland 
69570d30c2SSamuel Holland static struct bus_type coreboot_bus_type = {
70570d30c2SSamuel Holland 	.name		= "coreboot",
71570d30c2SSamuel Holland 	.match		= coreboot_bus_match,
72570d30c2SSamuel Holland 	.probe		= coreboot_bus_probe,
73570d30c2SSamuel Holland 	.remove		= coreboot_bus_remove,
74570d30c2SSamuel Holland };
75570d30c2SSamuel Holland 
76570d30c2SSamuel Holland static void coreboot_device_release(struct device *dev)
77570d30c2SSamuel Holland {
78570d30c2SSamuel Holland 	struct coreboot_device *device = CB_DEV(dev);
79570d30c2SSamuel Holland 
80570d30c2SSamuel Holland 	kfree(device);
81570d30c2SSamuel Holland }
82570d30c2SSamuel Holland 
83570d30c2SSamuel Holland int coreboot_driver_register(struct coreboot_driver *driver)
84570d30c2SSamuel Holland {
85570d30c2SSamuel Holland 	driver->drv.bus = &coreboot_bus_type;
86570d30c2SSamuel Holland 
87570d30c2SSamuel Holland 	return driver_register(&driver->drv);
88570d30c2SSamuel Holland }
89570d30c2SSamuel Holland EXPORT_SYMBOL(coreboot_driver_register);
90570d30c2SSamuel Holland 
91570d30c2SSamuel Holland void coreboot_driver_unregister(struct coreboot_driver *driver)
92570d30c2SSamuel Holland {
93570d30c2SSamuel Holland 	driver_unregister(&driver->drv);
94570d30c2SSamuel Holland }
95570d30c2SSamuel Holland EXPORT_SYMBOL(coreboot_driver_unregister);
96570d30c2SSamuel Holland 
97a28aad66SStephen Boyd static int coreboot_table_init(struct device *dev, void __iomem *ptr)
98d384d6f4SThierry Escande {
99570d30c2SSamuel Holland 	int i, ret;
100570d30c2SSamuel Holland 	void *ptr_entry;
101570d30c2SSamuel Holland 	struct coreboot_device *device;
102570d30c2SSamuel Holland 	struct coreboot_table_entry entry;
103570d30c2SSamuel Holland 	struct coreboot_table_header header;
104d384d6f4SThierry Escande 
105570d30c2SSamuel Holland 	ptr_header = ptr;
106570d30c2SSamuel Holland 	memcpy_fromio(&header, ptr_header, sizeof(header));
107570d30c2SSamuel Holland 
108570d30c2SSamuel Holland 	if (strncmp(header.signature, "LBIO", sizeof(header.signature))) {
109570d30c2SSamuel Holland 		pr_warn("coreboot_table: coreboot table missing or corrupt!\n");
11020edec38SStephen Boyd 		ret = -ENODEV;
11120edec38SStephen Boyd 		goto out;
112570d30c2SSamuel Holland 	}
113570d30c2SSamuel Holland 
114b81e3140SStephen Boyd 	ret = bus_register(&coreboot_bus_type);
115b81e3140SStephen Boyd 	if (ret)
116b81e3140SStephen Boyd 		goto out;
117b81e3140SStephen Boyd 
118570d30c2SSamuel Holland 	ptr_entry = (void *)ptr_header + header.header_bytes;
119570d30c2SSamuel Holland 	for (i = 0; i < header.table_entries; i++) {
120570d30c2SSamuel Holland 		memcpy_fromio(&entry, ptr_entry, sizeof(entry));
121570d30c2SSamuel Holland 
122570d30c2SSamuel Holland 		device = kzalloc(sizeof(struct device) + entry.size, GFP_KERNEL);
123570d30c2SSamuel Holland 		if (!device) {
124570d30c2SSamuel Holland 			ret = -ENOMEM;
125570d30c2SSamuel Holland 			break;
126570d30c2SSamuel Holland 		}
127570d30c2SSamuel Holland 
128570d30c2SSamuel Holland 		dev_set_name(&device->dev, "coreboot%d", i);
129570d30c2SSamuel Holland 		device->dev.parent = dev;
130570d30c2SSamuel Holland 		device->dev.bus = &coreboot_bus_type;
131570d30c2SSamuel Holland 		device->dev.release = coreboot_device_release;
132570d30c2SSamuel Holland 		memcpy_fromio(&device->entry, ptr_entry, entry.size);
133570d30c2SSamuel Holland 
134570d30c2SSamuel Holland 		ret = device_register(&device->dev);
135570d30c2SSamuel Holland 		if (ret) {
136570d30c2SSamuel Holland 			put_device(&device->dev);
137570d30c2SSamuel Holland 			break;
138570d30c2SSamuel Holland 		}
139570d30c2SSamuel Holland 
140570d30c2SSamuel Holland 		ptr_entry += entry.size;
141570d30c2SSamuel Holland 	}
142b81e3140SStephen Boyd 
143b81e3140SStephen Boyd 	if (ret)
144b81e3140SStephen Boyd 		bus_unregister(&coreboot_bus_type);
145b81e3140SStephen Boyd 
14620edec38SStephen Boyd out:
14720edec38SStephen Boyd 	iounmap(ptr);
148570d30c2SSamuel Holland 	return ret;
149d384d6f4SThierry Escande }
150d384d6f4SThierry Escande 
151a28aad66SStephen Boyd static int coreboot_table_probe(struct platform_device *pdev)
152a28aad66SStephen Boyd {
153a28aad66SStephen Boyd 	phys_addr_t phyaddr;
154a28aad66SStephen Boyd 	resource_size_t len;
155a28aad66SStephen Boyd 	struct coreboot_table_header __iomem *header = NULL;
156a28aad66SStephen Boyd 	struct resource *res;
157a28aad66SStephen Boyd 	void __iomem *ptr = NULL;
158a28aad66SStephen Boyd 
159a28aad66SStephen Boyd 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
160a28aad66SStephen Boyd 	if (!res)
161a28aad66SStephen Boyd 		return -EINVAL;
162a28aad66SStephen Boyd 
163a28aad66SStephen Boyd 	len = resource_size(res);
164a28aad66SStephen Boyd 	if (!res->start || !len)
165a28aad66SStephen Boyd 		return -EINVAL;
166a28aad66SStephen Boyd 
167a28aad66SStephen Boyd 	phyaddr = res->start;
168a28aad66SStephen Boyd 	header = ioremap_cache(phyaddr, sizeof(*header));
169a28aad66SStephen Boyd 	if (header == NULL)
170a28aad66SStephen Boyd 		return -ENOMEM;
171a28aad66SStephen Boyd 
172a28aad66SStephen Boyd 	ptr = ioremap_cache(phyaddr,
173a28aad66SStephen Boyd 			    header->header_bytes + header->table_bytes);
174a28aad66SStephen Boyd 	iounmap(header);
175a28aad66SStephen Boyd 	if (!ptr)
176a28aad66SStephen Boyd 		return -ENOMEM;
177a28aad66SStephen Boyd 
178a28aad66SStephen Boyd 	return coreboot_table_init(&pdev->dev, ptr);
179a28aad66SStephen Boyd }
180a28aad66SStephen Boyd 
181a28aad66SStephen Boyd static int coreboot_table_remove(struct platform_device *pdev)
182d384d6f4SThierry Escande {
183570d30c2SSamuel Holland 	if (ptr_header) {
184570d30c2SSamuel Holland 		bus_unregister(&coreboot_bus_type);
185570d30c2SSamuel Holland 		ptr_header = NULL;
186570d30c2SSamuel Holland 	}
187d384d6f4SThierry Escande 
188d384d6f4SThierry Escande 	return 0;
189d384d6f4SThierry Escande }
190d384d6f4SThierry Escande 
191a28aad66SStephen Boyd #ifdef CONFIG_ACPI
192a28aad66SStephen Boyd static const struct acpi_device_id cros_coreboot_acpi_match[] = {
193a28aad66SStephen Boyd 	{ "GOOGCB00", 0 },
194a28aad66SStephen Boyd 	{ "BOOT0000", 0 },
195a28aad66SStephen Boyd 	{ }
196a28aad66SStephen Boyd };
197a28aad66SStephen Boyd MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match);
198a28aad66SStephen Boyd #endif
199a28aad66SStephen Boyd 
200a28aad66SStephen Boyd #ifdef CONFIG_OF
201a28aad66SStephen Boyd static const struct of_device_id coreboot_of_match[] = {
202a28aad66SStephen Boyd 	{ .compatible = "coreboot" },
203a28aad66SStephen Boyd 	{}
204a28aad66SStephen Boyd };
205a28aad66SStephen Boyd MODULE_DEVICE_TABLE(of, coreboot_of_match);
206a28aad66SStephen Boyd #endif
207a28aad66SStephen Boyd 
208a28aad66SStephen Boyd static struct platform_driver coreboot_table_driver = {
209a28aad66SStephen Boyd 	.probe = coreboot_table_probe,
210a28aad66SStephen Boyd 	.remove = coreboot_table_remove,
211a28aad66SStephen Boyd 	.driver = {
212a28aad66SStephen Boyd 		.name = "coreboot_table",
213a28aad66SStephen Boyd 		.acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match),
214a28aad66SStephen Boyd 		.of_match_table = of_match_ptr(coreboot_of_match),
215a28aad66SStephen Boyd 	},
216a28aad66SStephen Boyd };
217a28aad66SStephen Boyd module_platform_driver(coreboot_table_driver);
218d384d6f4SThierry Escande MODULE_AUTHOR("Google, Inc.");
219d384d6f4SThierry Escande MODULE_LICENSE("GPL");
220