1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright 2018-2020 NXP. |
4 | */ |
5 | |
6 | #include <dt-bindings/firmware/imx/rsrc.h> |
7 | #include <linux/err.h> |
8 | #include <linux/firmware/imx/sci.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/thermal.h> |
14 | |
15 | #include "thermal_hwmon.h" |
16 | |
17 | #define IMX_SC_MISC_FUNC_GET_TEMP 13 |
18 | |
19 | static struct imx_sc_ipc *thermal_ipc_handle; |
20 | |
21 | struct imx_sc_sensor { |
22 | struct thermal_zone_device *tzd; |
23 | u32 resource_id; |
24 | }; |
25 | |
26 | struct req_get_temp { |
27 | u16 resource_id; |
28 | u8 type; |
29 | } __packed __aligned(4); |
30 | |
31 | struct resp_get_temp { |
32 | s16 celsius; |
33 | s8 tenths; |
34 | } __packed __aligned(4); |
35 | |
36 | struct imx_sc_msg_misc_get_temp { |
37 | struct imx_sc_rpc_msg hdr; |
38 | union { |
39 | struct req_get_temp req; |
40 | struct resp_get_temp resp; |
41 | } data; |
42 | } __packed __aligned(4); |
43 | |
44 | static int imx_sc_thermal_get_temp(struct thermal_zone_device *tz, int *temp) |
45 | { |
46 | struct imx_sc_msg_misc_get_temp msg; |
47 | struct imx_sc_rpc_msg *hdr = &msg.hdr; |
48 | struct imx_sc_sensor *sensor = thermal_zone_device_priv(tzd: tz); |
49 | int ret; |
50 | |
51 | msg.data.req.resource_id = sensor->resource_id; |
52 | msg.data.req.type = IMX_SC_C_TEMP; |
53 | |
54 | hdr->ver = IMX_SC_RPC_VERSION; |
55 | hdr->svc = IMX_SC_RPC_SVC_MISC; |
56 | hdr->func = IMX_SC_MISC_FUNC_GET_TEMP; |
57 | hdr->size = 2; |
58 | |
59 | ret = imx_scu_call_rpc(ipc: thermal_ipc_handle, msg: &msg, have_resp: true); |
60 | if (ret) |
61 | return ret; |
62 | |
63 | *temp = msg.data.resp.celsius * 1000 + msg.data.resp.tenths * 100; |
64 | |
65 | return 0; |
66 | } |
67 | |
68 | static const struct thermal_zone_device_ops imx_sc_thermal_ops = { |
69 | .get_temp = imx_sc_thermal_get_temp, |
70 | }; |
71 | |
72 | static int imx_sc_thermal_probe(struct platform_device *pdev) |
73 | { |
74 | struct imx_sc_sensor *sensor; |
75 | const int *resource_id; |
76 | int i, ret; |
77 | |
78 | ret = imx_scu_get_handle(ipc: &thermal_ipc_handle); |
79 | if (ret) |
80 | return ret; |
81 | |
82 | resource_id = of_device_get_match_data(dev: &pdev->dev); |
83 | if (!resource_id) |
84 | return -EINVAL; |
85 | |
86 | for (i = 0; resource_id[i] >= 0; i++) { |
87 | |
88 | sensor = devm_kzalloc(dev: &pdev->dev, size: sizeof(*sensor), GFP_KERNEL); |
89 | if (!sensor) |
90 | return -ENOMEM; |
91 | |
92 | sensor->resource_id = resource_id[i]; |
93 | |
94 | sensor->tzd = devm_thermal_of_zone_register(dev: &pdev->dev, id: sensor->resource_id, |
95 | data: sensor, ops: &imx_sc_thermal_ops); |
96 | if (IS_ERR(ptr: sensor->tzd)) { |
97 | /* |
98 | * Save the error value before freeing the |
99 | * sensor pointer, otherwise we endup with a |
100 | * use-after-free error |
101 | */ |
102 | ret = PTR_ERR(ptr: sensor->tzd); |
103 | |
104 | devm_kfree(dev: &pdev->dev, p: sensor); |
105 | |
106 | /* |
107 | * The thermal framework notifies us there is |
108 | * no thermal zone description for such a |
109 | * sensor id |
110 | */ |
111 | if (ret == -ENODEV) |
112 | continue; |
113 | |
114 | dev_err(&pdev->dev, "failed to register thermal zone\n" ); |
115 | return ret; |
116 | } |
117 | |
118 | devm_thermal_add_hwmon_sysfs(dev: &pdev->dev, tz: sensor->tzd); |
119 | } |
120 | |
121 | return 0; |
122 | } |
123 | |
124 | static const int imx_sc_sensors[] = { |
125 | IMX_SC_R_SYSTEM, IMX_SC_R_PMIC_0, |
126 | IMX_SC_R_AP_0, IMX_SC_R_AP_1, |
127 | IMX_SC_R_GPU_0_PID0, IMX_SC_R_GPU_1_PID0, |
128 | IMX_SC_R_DRC_0, -1 }; |
129 | |
130 | static const struct of_device_id imx_sc_thermal_table[] = { |
131 | { .compatible = "fsl,imx-sc-thermal" , .data = imx_sc_sensors }, |
132 | {} |
133 | }; |
134 | MODULE_DEVICE_TABLE(of, imx_sc_thermal_table); |
135 | |
136 | static struct platform_driver imx_sc_thermal_driver = { |
137 | .probe = imx_sc_thermal_probe, |
138 | .driver = { |
139 | .name = "imx-sc-thermal" , |
140 | .of_match_table = imx_sc_thermal_table, |
141 | }, |
142 | }; |
143 | module_platform_driver(imx_sc_thermal_driver); |
144 | |
145 | MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>" ); |
146 | MODULE_DESCRIPTION("Thermal driver for NXP i.MX SoCs with system controller" ); |
147 | MODULE_LICENSE("GPL v2" ); |
148 | |