1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * sfr.c - driver for special function registers |
4 | * |
5 | * Copyright (C) 2019 Bootlin. |
6 | * |
7 | */ |
8 | #include <linux/mfd/syscon.h> |
9 | #include <linux/module.h> |
10 | #include <linux/nvmem-provider.h> |
11 | #include <linux/random.h> |
12 | #include <linux/of.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/regmap.h> |
15 | |
16 | #define SFR_SN0 0x4c |
17 | #define SFR_SN_SIZE 8 |
18 | |
19 | struct atmel_sfr_priv { |
20 | struct regmap *regmap; |
21 | }; |
22 | |
23 | static int atmel_sfr_read(void *context, unsigned int offset, |
24 | void *buf, size_t bytes) |
25 | { |
26 | struct atmel_sfr_priv *priv = context; |
27 | |
28 | return regmap_bulk_read(map: priv->regmap, SFR_SN0 + offset, |
29 | val: buf, val_count: bytes / 4); |
30 | } |
31 | |
32 | static struct nvmem_config atmel_sfr_nvmem_config = { |
33 | .name = "atmel-sfr" , |
34 | .read_only = true, |
35 | .word_size = 4, |
36 | .stride = 4, |
37 | .size = SFR_SN_SIZE, |
38 | .reg_read = atmel_sfr_read, |
39 | }; |
40 | |
41 | static int atmel_sfr_probe(struct platform_device *pdev) |
42 | { |
43 | struct device *dev = &pdev->dev; |
44 | struct device_node *np = dev->of_node; |
45 | struct nvmem_device *nvmem; |
46 | struct atmel_sfr_priv *priv; |
47 | u8 sn[SFR_SN_SIZE]; |
48 | int ret; |
49 | |
50 | priv = devm_kmalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
51 | if (!priv) |
52 | return -ENOMEM; |
53 | |
54 | priv->regmap = syscon_node_to_regmap(np); |
55 | if (IS_ERR(ptr: priv->regmap)) { |
56 | dev_err(dev, "cannot get parent's regmap\n" ); |
57 | return PTR_ERR(ptr: priv->regmap); |
58 | } |
59 | |
60 | atmel_sfr_nvmem_config.dev = dev; |
61 | atmel_sfr_nvmem_config.priv = priv; |
62 | |
63 | nvmem = devm_nvmem_register(dev, cfg: &atmel_sfr_nvmem_config); |
64 | if (IS_ERR(ptr: nvmem)) { |
65 | dev_err(dev, "error registering nvmem config\n" ); |
66 | return PTR_ERR(ptr: nvmem); |
67 | } |
68 | |
69 | ret = atmel_sfr_read(context: priv, offset: 0, buf: sn, SFR_SN_SIZE); |
70 | if (ret == 0) |
71 | add_device_randomness(buf: sn, SFR_SN_SIZE); |
72 | |
73 | return ret; |
74 | } |
75 | |
76 | static const struct of_device_id atmel_sfr_dt_ids[] = { |
77 | { |
78 | .compatible = "atmel,sama5d2-sfr" , |
79 | }, { |
80 | .compatible = "atmel,sama5d4-sfr" , |
81 | }, { |
82 | /* sentinel */ |
83 | }, |
84 | }; |
85 | MODULE_DEVICE_TABLE(of, atmel_sfr_dt_ids); |
86 | |
87 | static struct platform_driver atmel_sfr_driver = { |
88 | .probe = atmel_sfr_probe, |
89 | .driver = { |
90 | .name = "atmel-sfr" , |
91 | .of_match_table = atmel_sfr_dt_ids, |
92 | }, |
93 | }; |
94 | module_platform_driver(atmel_sfr_driver); |
95 | |
96 | MODULE_AUTHOR("Kamel Bouhara <kamel.bouhara@bootlin.com>" ); |
97 | MODULE_DESCRIPTION("Atmel SFR SN driver for SAMA5D2/4 SoC family" ); |
98 | MODULE_LICENSE("GPL v2" ); |
99 | |