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