Commit | Line | Data |
---|---|---|
423a54da WL |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (c) 2023 MediaTek Inc. | |
4 | */ | |
5 | #include <linux/kernel.h> | |
6 | #include <linux/module.h> | |
7 | #include <linux/of.h> | |
8 | #include <linux/of_platform.h> | |
9 | #include <linux/pm_runtime.h> | |
10 | #include <linux/nvmem-consumer.h> | |
11 | #include <linux/device.h> | |
82e5d7d7 | 12 | #include <linux/device/bus.h> |
423a54da WL |
13 | #include <linux/debugfs.h> |
14 | #include <linux/seq_file.h> | |
15 | #include <linux/string.h> | |
16 | #include <linux/sys_soc.h> | |
17 | #include <linux/slab.h> | |
18 | #include <linux/platform_device.h> | |
19 | ||
20 | #define MTK_SOCINFO_ENTRY(_soc_name, _segment_name, _marketing_name, _cell_data1, _cell_data2) {\ | |
21 | .soc_name = _soc_name, \ | |
22 | .segment_name = _segment_name, \ | |
23 | .marketing_name = _marketing_name, \ | |
24 | .cell_data = {_cell_data1, _cell_data2} \ | |
25 | } | |
26 | #define CELL_NOT_USED (0xFFFFFFFF) | |
27 | #define MAX_CELLS (2) | |
28 | ||
29 | struct mtk_socinfo { | |
30 | struct device *dev; | |
31 | struct name_data *name_data; | |
32 | struct socinfo_data *socinfo_data; | |
33 | struct soc_device *soc_dev; | |
34 | }; | |
35 | ||
36 | struct socinfo_data { | |
37 | char *soc_name; | |
38 | char *segment_name; | |
39 | char *marketing_name; | |
40 | u32 cell_data[MAX_CELLS]; | |
41 | }; | |
42 | ||
43 | static const char *cell_names[MAX_CELLS] = {"socinfo-data1", "socinfo-data2"}; | |
44 | ||
45 | static struct socinfo_data socinfo_data_table[] = { | |
46 | MTK_SOCINFO_ENTRY("MT8173", "MT8173V/AC", "MT8173", 0x6CA20004, 0x10000000), | |
47 | MTK_SOCINFO_ENTRY("MT8183", "MT8183V/AZA", "Kompanio 500", 0x00010043, 0x00000840), | |
54d21dea | 48 | MTK_SOCINFO_ENTRY("MT8183", "MT8183V/AZA", "Kompanio 500", 0x00010043, 0x00000940), |
423a54da WL |
49 | MTK_SOCINFO_ENTRY("MT8186", "MT8186GV/AZA", "Kompanio 520", 0x81861001, CELL_NOT_USED), |
50 | MTK_SOCINFO_ENTRY("MT8186T", "MT8186TV/AZA", "Kompanio 528", 0x81862001, CELL_NOT_USED), | |
51 | MTK_SOCINFO_ENTRY("MT8188", "MT8188GV/AZA", "Kompanio 830", 0x81880000, 0x00000010), | |
52 | MTK_SOCINFO_ENTRY("MT8188", "MT8188GV/HZA", "Kompanio 830", 0x81880000, 0x00000011), | |
53 | MTK_SOCINFO_ENTRY("MT8192", "MT8192V/AZA", "Kompanio 820", 0x00001100, 0x00040080), | |
54 | MTK_SOCINFO_ENTRY("MT8192T", "MT8192V/ATZA", "Kompanio 828", 0x00000100, 0x000400C0), | |
55 | MTK_SOCINFO_ENTRY("MT8195", "MT8195GV/EZA", "Kompanio 1200", 0x81950300, CELL_NOT_USED), | |
56 | MTK_SOCINFO_ENTRY("MT8195", "MT8195GV/EHZA", "Kompanio 1200", 0x81950304, CELL_NOT_USED), | |
57 | MTK_SOCINFO_ENTRY("MT8195", "MT8195TV/EZA", "Kompanio 1380", 0x81950400, CELL_NOT_USED), | |
58 | MTK_SOCINFO_ENTRY("MT8195", "MT8195TV/EHZA", "Kompanio 1380", 0x81950404, CELL_NOT_USED), | |
bc98f77d | 59 | MTK_SOCINFO_ENTRY("MT8395", "MT8395AV/ZA", "Genio 1200", 0x83950100, CELL_NOT_USED), |
423a54da WL |
60 | }; |
61 | ||
62 | static int mtk_socinfo_create_socinfo_node(struct mtk_socinfo *mtk_socinfop) | |
63 | { | |
64 | struct soc_device_attribute *attrs; | |
65 | static char machine[30] = {0}; | |
66 | static const char *soc_manufacturer = "MediaTek"; | |
67 | ||
68 | attrs = devm_kzalloc(mtk_socinfop->dev, sizeof(*attrs), GFP_KERNEL); | |
69 | if (!attrs) | |
70 | return -ENOMEM; | |
71 | ||
72 | snprintf(machine, sizeof(machine), "%s (%s)", mtk_socinfop->socinfo_data->marketing_name, | |
73 | mtk_socinfop->socinfo_data->soc_name); | |
74 | attrs->family = soc_manufacturer; | |
75 | attrs->machine = machine; | |
76 | ||
77 | mtk_socinfop->soc_dev = soc_device_register(attrs); | |
78 | if (IS_ERR(mtk_socinfop->soc_dev)) | |
79 | return PTR_ERR(mtk_socinfop->soc_dev); | |
80 | ||
81 | dev_info(mtk_socinfop->dev, "%s %s SoC detected.\n", soc_manufacturer, attrs->machine); | |
82 | return 0; | |
83 | } | |
84 | ||
85 | static u32 mtk_socinfo_read_cell(struct device *dev, const char *name) | |
86 | { | |
87 | struct nvmem_device *nvmemp; | |
82e5d7d7 | 88 | struct device_node *np, *nvmem_node = dev->parent->of_node; |
423a54da WL |
89 | u32 offset; |
90 | u32 cell_val = CELL_NOT_USED; | |
91 | ||
82e5d7d7 CYT |
92 | /* should never fail since the nvmem driver registers this child */ |
93 | nvmemp = nvmem_device_find(nvmem_node, device_match_of_node); | |
423a54da WL |
94 | if (IS_ERR(nvmemp)) |
95 | goto out; | |
96 | ||
82e5d7d7 | 97 | np = of_get_child_by_name(nvmem_node, name); |
423a54da | 98 | if (!np) |
82e5d7d7 | 99 | goto put_device; |
423a54da WL |
100 | |
101 | if (of_property_read_u32_index(np, "reg", 0, &offset)) | |
82e5d7d7 | 102 | goto put_node; |
423a54da WL |
103 | |
104 | nvmem_device_read(nvmemp, offset, sizeof(cell_val), &cell_val); | |
105 | ||
82e5d7d7 CYT |
106 | put_node: |
107 | of_node_put(np); | |
108 | put_device: | |
423a54da | 109 | nvmem_device_put(nvmemp); |
423a54da WL |
110 | out: |
111 | return cell_val; | |
112 | } | |
113 | ||
114 | static int mtk_socinfo_get_socinfo_data(struct mtk_socinfo *mtk_socinfop) | |
115 | { | |
116 | unsigned int i, j; | |
117 | unsigned int num_cell_data = 0; | |
118 | u32 cell_data[MAX_CELLS] = {0}; | |
119 | bool match_socinfo; | |
120 | int match_socinfo_index = -1; | |
121 | ||
122 | for (i = 0; i < MAX_CELLS; i++) { | |
123 | cell_data[i] = mtk_socinfo_read_cell(mtk_socinfop->dev, cell_names[i]); | |
124 | if (cell_data[i] != CELL_NOT_USED) | |
125 | num_cell_data++; | |
126 | else | |
127 | break; | |
128 | } | |
129 | ||
130 | if (!num_cell_data) | |
131 | return -ENOENT; | |
132 | ||
133 | for (i = 0; i < ARRAY_SIZE(socinfo_data_table); i++) { | |
134 | match_socinfo = true; | |
135 | for (j = 0; j < num_cell_data; j++) { | |
136 | if (cell_data[j] != socinfo_data_table[i].cell_data[j]) { | |
137 | match_socinfo = false; | |
138 | break; | |
139 | } | |
140 | } | |
141 | if (match_socinfo) { | |
142 | mtk_socinfop->socinfo_data = &(socinfo_data_table[i]); | |
143 | match_socinfo_index = i; | |
144 | break; | |
145 | } | |
146 | } | |
147 | ||
7843b6b8 ADR |
148 | if (match_socinfo_index < 0) { |
149 | dev_warn(mtk_socinfop->dev, | |
150 | "Unknown MediaTek SoC with ID 0x%08x 0x%08x\n", | |
151 | cell_data[0], cell_data[1]); | |
152 | return -ENOENT; | |
153 | } | |
154 | ||
155 | return match_socinfo_index; | |
423a54da WL |
156 | } |
157 | ||
158 | static int mtk_socinfo_probe(struct platform_device *pdev) | |
159 | { | |
160 | struct mtk_socinfo *mtk_socinfop; | |
161 | int ret; | |
162 | ||
163 | mtk_socinfop = devm_kzalloc(&pdev->dev, sizeof(*mtk_socinfop), GFP_KERNEL); | |
164 | if (!mtk_socinfop) | |
165 | return -ENOMEM; | |
166 | ||
167 | mtk_socinfop->dev = &pdev->dev; | |
168 | ||
169 | ret = mtk_socinfo_get_socinfo_data(mtk_socinfop); | |
170 | if (ret < 0) | |
171 | return dev_err_probe(mtk_socinfop->dev, ret, "Failed to get socinfo data\n"); | |
172 | ||
173 | ret = mtk_socinfo_create_socinfo_node(mtk_socinfop); | |
174 | if (ret) | |
175 | return dev_err_probe(mtk_socinfop->dev, ret, "Cannot create node\n"); | |
176 | ||
177 | platform_set_drvdata(pdev, mtk_socinfop); | |
178 | return 0; | |
179 | } | |
180 | ||
181 | static void mtk_socinfo_remove(struct platform_device *pdev) | |
182 | { | |
183 | struct mtk_socinfo *mtk_socinfop = platform_get_drvdata(pdev); | |
184 | ||
185 | soc_device_unregister(mtk_socinfop->soc_dev); | |
186 | } | |
187 | ||
188 | static struct platform_driver mtk_socinfo = { | |
189 | .probe = mtk_socinfo_probe, | |
190 | .remove_new = mtk_socinfo_remove, | |
191 | .driver = { | |
192 | .name = "mtk-socinfo", | |
193 | }, | |
194 | }; | |
195 | module_platform_driver(mtk_socinfo); | |
196 | ||
197 | MODULE_AUTHOR("William-TW LIN <william-tw.lin@mediatek.com>"); | |
198 | MODULE_DESCRIPTION("MediaTek socinfo driver"); | |
199 | MODULE_LICENSE("GPL"); |