1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2015 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de> |
4 | * Copyright (c) 2017 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> |
5 | */ |
6 | |
7 | #include <linux/mfd/syscon.h> |
8 | #include <linux/module.h> |
9 | #include <linux/nvmem-provider.h> |
10 | #include <linux/of.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/regmap.h> |
13 | |
14 | #define IMX6Q_SNVS_HPLR 0x00 |
15 | #define IMX6Q_SNVS_LPLR 0x34 |
16 | #define IMX6Q_SNVS_LPGPR 0x68 |
17 | |
18 | #define IMX7D_SNVS_HPLR 0x00 |
19 | #define IMX7D_SNVS_LPLR 0x34 |
20 | #define IMX7D_SNVS_LPGPR 0x90 |
21 | |
22 | #define IMX_GPR_SL BIT(5) |
23 | #define IMX_GPR_HL BIT(5) |
24 | |
25 | struct snvs_lpgpr_cfg { |
26 | int offset; |
27 | int offset_hplr; |
28 | int offset_lplr; |
29 | int size; |
30 | }; |
31 | |
32 | struct snvs_lpgpr_priv { |
33 | struct device_d *dev; |
34 | struct regmap *regmap; |
35 | struct nvmem_config cfg; |
36 | const struct snvs_lpgpr_cfg *dcfg; |
37 | }; |
38 | |
39 | static const struct snvs_lpgpr_cfg snvs_lpgpr_cfg_imx6q = { |
40 | .offset = IMX6Q_SNVS_LPGPR, |
41 | .offset_hplr = IMX6Q_SNVS_HPLR, |
42 | .offset_lplr = IMX6Q_SNVS_LPLR, |
43 | .size = 4, |
44 | }; |
45 | |
46 | static const struct snvs_lpgpr_cfg snvs_lpgpr_cfg_imx7d = { |
47 | .offset = IMX7D_SNVS_LPGPR, |
48 | .offset_hplr = IMX7D_SNVS_HPLR, |
49 | .offset_lplr = IMX7D_SNVS_LPLR, |
50 | .size = 16, |
51 | }; |
52 | |
53 | static int snvs_lpgpr_write(void *context, unsigned int offset, void *val, |
54 | size_t bytes) |
55 | { |
56 | struct snvs_lpgpr_priv *priv = context; |
57 | const struct snvs_lpgpr_cfg *dcfg = priv->dcfg; |
58 | unsigned int lock_reg; |
59 | int ret; |
60 | |
61 | ret = regmap_read(map: priv->regmap, reg: dcfg->offset_hplr, val: &lock_reg); |
62 | if (ret < 0) |
63 | return ret; |
64 | |
65 | if (lock_reg & IMX_GPR_SL) |
66 | return -EPERM; |
67 | |
68 | ret = regmap_read(map: priv->regmap, reg: dcfg->offset_lplr, val: &lock_reg); |
69 | if (ret < 0) |
70 | return ret; |
71 | |
72 | if (lock_reg & IMX_GPR_HL) |
73 | return -EPERM; |
74 | |
75 | return regmap_bulk_write(map: priv->regmap, reg: dcfg->offset + offset, val, |
76 | val_count: bytes / 4); |
77 | } |
78 | |
79 | static int snvs_lpgpr_read(void *context, unsigned int offset, void *val, |
80 | size_t bytes) |
81 | { |
82 | struct snvs_lpgpr_priv *priv = context; |
83 | const struct snvs_lpgpr_cfg *dcfg = priv->dcfg; |
84 | |
85 | return regmap_bulk_read(map: priv->regmap, reg: dcfg->offset + offset, |
86 | val, val_count: bytes / 4); |
87 | } |
88 | |
89 | static int snvs_lpgpr_probe(struct platform_device *pdev) |
90 | { |
91 | struct device *dev = &pdev->dev; |
92 | struct device_node *node = dev->of_node; |
93 | struct device_node *syscon_node; |
94 | struct snvs_lpgpr_priv *priv; |
95 | struct nvmem_config *cfg; |
96 | struct nvmem_device *nvmem; |
97 | const struct snvs_lpgpr_cfg *dcfg; |
98 | |
99 | if (!node) |
100 | return -ENOENT; |
101 | |
102 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
103 | if (!priv) |
104 | return -ENOMEM; |
105 | |
106 | dcfg = of_device_get_match_data(dev); |
107 | if (!dcfg) |
108 | return -EINVAL; |
109 | |
110 | syscon_node = of_get_parent(node); |
111 | if (!syscon_node) |
112 | return -ENODEV; |
113 | |
114 | priv->regmap = syscon_node_to_regmap(np: syscon_node); |
115 | of_node_put(node: syscon_node); |
116 | if (IS_ERR(ptr: priv->regmap)) |
117 | return PTR_ERR(ptr: priv->regmap); |
118 | |
119 | priv->dcfg = dcfg; |
120 | |
121 | cfg = &priv->cfg; |
122 | cfg->priv = priv; |
123 | cfg->name = dev_name(dev); |
124 | cfg->dev = dev; |
125 | cfg->stride = 4; |
126 | cfg->word_size = 4; |
127 | cfg->size = dcfg->size; |
128 | cfg->owner = THIS_MODULE; |
129 | cfg->reg_read = snvs_lpgpr_read; |
130 | cfg->reg_write = snvs_lpgpr_write; |
131 | |
132 | nvmem = devm_nvmem_register(dev, cfg); |
133 | |
134 | return PTR_ERR_OR_ZERO(ptr: nvmem); |
135 | } |
136 | |
137 | static const struct of_device_id snvs_lpgpr_dt_ids[] = { |
138 | { .compatible = "fsl,imx6q-snvs-lpgpr" , .data = &snvs_lpgpr_cfg_imx6q }, |
139 | { .compatible = "fsl,imx6ul-snvs-lpgpr" , |
140 | .data = &snvs_lpgpr_cfg_imx6q }, |
141 | { .compatible = "fsl,imx7d-snvs-lpgpr" , .data = &snvs_lpgpr_cfg_imx7d }, |
142 | { }, |
143 | }; |
144 | MODULE_DEVICE_TABLE(of, snvs_lpgpr_dt_ids); |
145 | |
146 | static struct platform_driver snvs_lpgpr_driver = { |
147 | .probe = snvs_lpgpr_probe, |
148 | .driver = { |
149 | .name = "snvs_lpgpr" , |
150 | .of_match_table = snvs_lpgpr_dt_ids, |
151 | }, |
152 | }; |
153 | module_platform_driver(snvs_lpgpr_driver); |
154 | |
155 | MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>" ); |
156 | MODULE_DESCRIPTION("Low Power General Purpose Register in i.MX6 and i.MX7 Secure Non-Volatile Storage" ); |
157 | MODULE_LICENSE("GPL v2" ); |
158 | |