1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* Sparx5 SoC temperature sensor driver |
3 | * |
4 | * Copyright (C) 2020 Lars Povlsen <lars.povlsen@microchip.com> |
5 | */ |
6 | |
7 | #include <linux/bitfield.h> |
8 | #include <linux/clk.h> |
9 | #include <linux/hwmon.h> |
10 | #include <linux/init.h> |
11 | #include <linux/io.h> |
12 | #include <linux/mod_devicetable.h> |
13 | #include <linux/module.h> |
14 | #include <linux/platform_device.h> |
15 | |
16 | #define TEMP_CTRL 0 |
17 | #define TEMP_CFG 4 |
18 | #define TEMP_CFG_CYCLES GENMASK(24, 15) |
19 | #define TEMP_CFG_ENA BIT(0) |
20 | #define TEMP_STAT 8 |
21 | #define TEMP_STAT_VALID BIT(12) |
22 | #define TEMP_STAT_TEMP GENMASK(11, 0) |
23 | |
24 | struct s5_hwmon { |
25 | void __iomem *base; |
26 | struct clk *clk; |
27 | }; |
28 | |
29 | static void s5_temp_enable(struct s5_hwmon *hwmon) |
30 | { |
31 | u32 val = readl(addr: hwmon->base + TEMP_CFG); |
32 | u32 clk = clk_get_rate(clk: hwmon->clk) / USEC_PER_SEC; |
33 | |
34 | val &= ~TEMP_CFG_CYCLES; |
35 | val |= FIELD_PREP(TEMP_CFG_CYCLES, clk); |
36 | val |= TEMP_CFG_ENA; |
37 | |
38 | writel(val, addr: hwmon->base + TEMP_CFG); |
39 | } |
40 | |
41 | static int s5_read(struct device *dev, enum hwmon_sensor_types type, |
42 | u32 attr, int channel, long *temp) |
43 | { |
44 | struct s5_hwmon *hwmon = dev_get_drvdata(dev); |
45 | int rc = 0, value; |
46 | u32 stat; |
47 | |
48 | switch (attr) { |
49 | case hwmon_temp_input: |
50 | stat = readl_relaxed(hwmon->base + TEMP_STAT); |
51 | if (!(stat & TEMP_STAT_VALID)) |
52 | return -EAGAIN; |
53 | value = stat & TEMP_STAT_TEMP; |
54 | /* |
55 | * From register documentation: |
56 | * Temp(C) = TEMP_SENSOR_STAT.TEMP / 4096 * 352.2 - 109.4 |
57 | */ |
58 | value = DIV_ROUND_CLOSEST(value * 3522, 4096) - 1094; |
59 | /* |
60 | * Scale down by 10 from above and multiply by 1000 to |
61 | * have millidegrees as specified by the hwmon sysfs |
62 | * interface. |
63 | */ |
64 | value *= 100; |
65 | *temp = value; |
66 | break; |
67 | default: |
68 | rc = -EOPNOTSUPP; |
69 | break; |
70 | } |
71 | |
72 | return rc; |
73 | } |
74 | |
75 | static umode_t s5_is_visible(const void *_data, enum hwmon_sensor_types type, |
76 | u32 attr, int channel) |
77 | { |
78 | if (type != hwmon_temp) |
79 | return 0; |
80 | |
81 | switch (attr) { |
82 | case hwmon_temp_input: |
83 | return 0444; |
84 | default: |
85 | return 0; |
86 | } |
87 | } |
88 | |
89 | static const struct hwmon_channel_info * const s5_info[] = { |
90 | HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), |
91 | HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), |
92 | NULL |
93 | }; |
94 | |
95 | static const struct hwmon_ops s5_hwmon_ops = { |
96 | .is_visible = s5_is_visible, |
97 | .read = s5_read, |
98 | }; |
99 | |
100 | static const struct hwmon_chip_info s5_chip_info = { |
101 | .ops = &s5_hwmon_ops, |
102 | .info = s5_info, |
103 | }; |
104 | |
105 | static int s5_temp_probe(struct platform_device *pdev) |
106 | { |
107 | struct device *hwmon_dev; |
108 | struct s5_hwmon *hwmon; |
109 | |
110 | hwmon = devm_kzalloc(dev: &pdev->dev, size: sizeof(*hwmon), GFP_KERNEL); |
111 | if (!hwmon) |
112 | return -ENOMEM; |
113 | |
114 | hwmon->base = devm_platform_ioremap_resource(pdev, index: 0); |
115 | if (IS_ERR(ptr: hwmon->base)) |
116 | return PTR_ERR(ptr: hwmon->base); |
117 | |
118 | hwmon->clk = devm_clk_get_enabled(dev: &pdev->dev, NULL); |
119 | if (IS_ERR(ptr: hwmon->clk)) |
120 | return PTR_ERR(ptr: hwmon->clk); |
121 | |
122 | s5_temp_enable(hwmon); |
123 | |
124 | hwmon_dev = devm_hwmon_device_register_with_info(dev: &pdev->dev, |
125 | name: "s5_temp" , |
126 | drvdata: hwmon, |
127 | info: &s5_chip_info, |
128 | NULL); |
129 | |
130 | return PTR_ERR_OR_ZERO(ptr: hwmon_dev); |
131 | } |
132 | |
133 | static const struct of_device_id s5_temp_match[] = { |
134 | { .compatible = "microchip,sparx5-temp" }, |
135 | {}, |
136 | }; |
137 | MODULE_DEVICE_TABLE(of, s5_temp_match); |
138 | |
139 | static struct platform_driver s5_temp_driver = { |
140 | .probe = s5_temp_probe, |
141 | .driver = { |
142 | .name = "sparx5-temp" , |
143 | .of_match_table = s5_temp_match, |
144 | }, |
145 | }; |
146 | |
147 | module_platform_driver(s5_temp_driver); |
148 | |
149 | MODULE_AUTHOR("Lars Povlsen <lars.povlsen@microchip.com>" ); |
150 | MODULE_DESCRIPTION("Sparx5 SoC temperature sensor driver" ); |
151 | MODULE_LICENSE("GPL" ); |
152 | |