1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2022 Rafał Miłecki <rafal@milecki.pl> |
4 | */ |
5 | |
6 | #include <linux/mod_devicetable.h> |
7 | #include <linux/module.h> |
8 | #include <linux/mtd/mtd.h> |
9 | #include <linux/nvmem-provider.h> |
10 | #include <linux/of.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/slab.h> |
13 | |
14 | #include "layouts/u-boot-env.h" |
15 | |
16 | struct u_boot_env { |
17 | struct device *dev; |
18 | struct nvmem_device *nvmem; |
19 | enum u_boot_env_format format; |
20 | |
21 | struct mtd_info *mtd; |
22 | }; |
23 | |
24 | static int u_boot_env_read(void *context, unsigned int offset, void *val, |
25 | size_t bytes) |
26 | { |
27 | struct u_boot_env *priv = context; |
28 | struct device *dev = priv->dev; |
29 | size_t bytes_read; |
30 | int err; |
31 | |
32 | err = mtd_read(mtd: priv->mtd, from: offset, len: bytes, retlen: &bytes_read, buf: val); |
33 | if (err && !mtd_is_bitflip(err)) { |
34 | dev_err(dev, "Failed to read from mtd: %d\n" , err); |
35 | return err; |
36 | } |
37 | |
38 | if (bytes_read != bytes) { |
39 | dev_err(dev, "Failed to read %zu bytes\n" , bytes); |
40 | return -EIO; |
41 | } |
42 | |
43 | return 0; |
44 | } |
45 | |
46 | static int u_boot_env_probe(struct platform_device *pdev) |
47 | { |
48 | struct nvmem_config config = { |
49 | .name = "u-boot-env" , |
50 | .reg_read = u_boot_env_read, |
51 | }; |
52 | struct device *dev = &pdev->dev; |
53 | struct device_node *np = dev->of_node; |
54 | struct u_boot_env *priv; |
55 | |
56 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
57 | if (!priv) |
58 | return -ENOMEM; |
59 | priv->dev = dev; |
60 | |
61 | priv->format = (uintptr_t)of_device_get_match_data(dev); |
62 | |
63 | priv->mtd = of_get_mtd_device_by_node(np); |
64 | if (IS_ERR(ptr: priv->mtd)) { |
65 | dev_err_probe(dev, err: PTR_ERR(ptr: priv->mtd), fmt: "Failed to get %pOF MTD\n" , np); |
66 | return PTR_ERR(ptr: priv->mtd); |
67 | } |
68 | |
69 | config.dev = dev; |
70 | config.priv = priv; |
71 | config.size = priv->mtd->size; |
72 | |
73 | priv->nvmem = devm_nvmem_register(dev, cfg: &config); |
74 | if (IS_ERR(ptr: priv->nvmem)) |
75 | return PTR_ERR(ptr: priv->nvmem); |
76 | |
77 | return u_boot_env_parse(dev, nvmem: priv->nvmem, format: priv->format); |
78 | } |
79 | |
80 | static const struct of_device_id u_boot_env_of_match_table[] = { |
81 | { .compatible = "u-boot,env" , .data = (void *)U_BOOT_FORMAT_SINGLE, }, |
82 | { .compatible = "u-boot,env-redundant-bool" , .data = (void *)U_BOOT_FORMAT_REDUNDANT, }, |
83 | { .compatible = "u-boot,env-redundant-count" , .data = (void *)U_BOOT_FORMAT_REDUNDANT, }, |
84 | { .compatible = "brcm,env" , .data = (void *)U_BOOT_FORMAT_BROADCOM, }, |
85 | {}, |
86 | }; |
87 | |
88 | static struct platform_driver u_boot_env_driver = { |
89 | .probe = u_boot_env_probe, |
90 | .driver = { |
91 | .name = "u_boot_env" , |
92 | .of_match_table = u_boot_env_of_match_table, |
93 | }, |
94 | }; |
95 | module_platform_driver(u_boot_env_driver); |
96 | |
97 | MODULE_AUTHOR("Rafał Miłecki" ); |
98 | MODULE_DESCRIPTION("U-Boot environment variables support module" ); |
99 | MODULE_LICENSE("GPL" ); |
100 | MODULE_DEVICE_TABLE(of, u_boot_env_of_match_table); |
101 | |