Merge branch 'dmi-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelvar...
[linux-2.6-microblaze.git] / drivers / hwmon / sparx5-temp.c
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_clk_disable(void *data)
30 {
31         struct clk *clk = data;
32
33         clk_disable_unprepare(clk);
34 }
35
36 static void s5_temp_enable(struct s5_hwmon *hwmon)
37 {
38         u32 val = readl(hwmon->base + TEMP_CFG);
39         u32 clk = clk_get_rate(hwmon->clk) / USEC_PER_SEC;
40
41         val &= ~TEMP_CFG_CYCLES;
42         val |= FIELD_PREP(TEMP_CFG_CYCLES, clk);
43         val |= TEMP_CFG_ENA;
44
45         writel(val, hwmon->base + TEMP_CFG);
46 }
47
48 static int s5_read(struct device *dev, enum hwmon_sensor_types type,
49                    u32 attr, int channel, long *temp)
50 {
51         struct s5_hwmon *hwmon = dev_get_drvdata(dev);
52         int rc = 0, value;
53         u32 stat;
54
55         switch (attr) {
56         case hwmon_temp_input:
57                 stat = readl_relaxed(hwmon->base + TEMP_STAT);
58                 if (!(stat & TEMP_STAT_VALID))
59                         return -EAGAIN;
60                 value = stat & TEMP_STAT_TEMP;
61                 /*
62                  * From register documentation:
63                  * Temp(C) = TEMP_SENSOR_STAT.TEMP / 4096 * 352.2 - 109.4
64                  */
65                 value = DIV_ROUND_CLOSEST(value * 3522, 4096) - 1094;
66                 /*
67                  * Scale down by 10 from above and multiply by 1000 to
68                  * have millidegrees as specified by the hwmon sysfs
69                  * interface.
70                  */
71                 value *= 100;
72                 *temp = value;
73                 break;
74         default:
75                 rc = -EOPNOTSUPP;
76                 break;
77         }
78
79         return rc;
80 }
81
82 static umode_t s5_is_visible(const void *_data, enum hwmon_sensor_types type,
83                              u32 attr, int channel)
84 {
85         if (type != hwmon_temp)
86                 return 0;
87
88         switch (attr) {
89         case hwmon_temp_input:
90                 return 0444;
91         default:
92                 return 0;
93         }
94 }
95
96 static const struct hwmon_channel_info *s5_info[] = {
97         HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
98         HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
99         NULL
100 };
101
102 static const struct hwmon_ops s5_hwmon_ops = {
103         .is_visible = s5_is_visible,
104         .read = s5_read,
105 };
106
107 static const struct hwmon_chip_info s5_chip_info = {
108         .ops = &s5_hwmon_ops,
109         .info = s5_info,
110 };
111
112 static int s5_temp_probe(struct platform_device *pdev)
113 {
114         struct device *hwmon_dev;
115         struct s5_hwmon *hwmon;
116         int ret;
117
118         hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);
119         if (!hwmon)
120                 return -ENOMEM;
121
122         hwmon->base = devm_platform_ioremap_resource(pdev, 0);
123         if (IS_ERR(hwmon->base))
124                 return PTR_ERR(hwmon->base);
125
126         hwmon->clk = devm_clk_get(&pdev->dev, NULL);
127         if (IS_ERR(hwmon->clk))
128                 return PTR_ERR(hwmon->clk);
129
130         ret = clk_prepare_enable(hwmon->clk);
131         if (ret)
132                 return ret;
133
134         ret = devm_add_action_or_reset(&pdev->dev, s5_temp_clk_disable,
135                                        hwmon->clk);
136         if (ret)
137                 return ret;
138
139         s5_temp_enable(hwmon);
140
141         hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
142                                                          "s5_temp",
143                                                          hwmon,
144                                                          &s5_chip_info,
145                                                          NULL);
146
147         return PTR_ERR_OR_ZERO(hwmon_dev);
148 }
149
150 static const struct of_device_id s5_temp_match[] = {
151         { .compatible = "microchip,sparx5-temp" },
152         {},
153 };
154 MODULE_DEVICE_TABLE(of, s5_temp_match);
155
156 static struct platform_driver s5_temp_driver = {
157         .probe = s5_temp_probe,
158         .driver = {
159                 .name = "sparx5-temp",
160                 .of_match_table = s5_temp_match,
161         },
162 };
163
164 module_platform_driver(s5_temp_driver);
165
166 MODULE_AUTHOR("Lars Povlsen <lars.povlsen@microchip.com>");
167 MODULE_DESCRIPTION("Sparx5 SoC temperature sensor driver");
168 MODULE_LICENSE("GPL");