1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Windfarm PowerMac thermal control. LM87 sensor |
4 | * |
5 | * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. |
6 | */ |
7 | |
8 | #include <linux/types.h> |
9 | #include <linux/errno.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/init.h> |
14 | #include <linux/wait.h> |
15 | #include <linux/i2c.h> |
16 | |
17 | #include <asm/machdep.h> |
18 | #include <asm/io.h> |
19 | #include <asm/sections.h> |
20 | #include <asm/pmac_low_i2c.h> |
21 | |
22 | #include "windfarm.h" |
23 | |
24 | #define VERSION "1.0" |
25 | |
26 | #undef DEBUG |
27 | |
28 | #ifdef DEBUG |
29 | #define DBG(args...) printk(args) |
30 | #else |
31 | #define DBG(args...) do { } while(0) |
32 | #endif |
33 | |
34 | struct wf_lm87_sensor { |
35 | struct i2c_client *i2c; |
36 | struct wf_sensor sens; |
37 | }; |
38 | #define wf_to_lm87(c) container_of(c, struct wf_lm87_sensor, sens) |
39 | |
40 | |
41 | static int wf_lm87_read_reg(struct i2c_client *chip, int reg) |
42 | { |
43 | int rc, tries = 0; |
44 | u8 buf; |
45 | |
46 | for (;;) { |
47 | /* Set address */ |
48 | buf = (u8)reg; |
49 | rc = i2c_master_send(client: chip, buf: &buf, count: 1); |
50 | if (rc <= 0) |
51 | goto error; |
52 | rc = i2c_master_recv(client: chip, buf: &buf, count: 1); |
53 | if (rc <= 0) |
54 | goto error; |
55 | return (int)buf; |
56 | error: |
57 | DBG("wf_lm87: Error reading LM87, retrying...\n" ); |
58 | if (++tries > 10) { |
59 | printk(KERN_ERR "wf_lm87: Error reading LM87 !\n" ); |
60 | return -EIO; |
61 | } |
62 | msleep(msecs: 10); |
63 | } |
64 | } |
65 | |
66 | static int wf_lm87_get(struct wf_sensor *sr, s32 *value) |
67 | { |
68 | struct wf_lm87_sensor *lm = sr->priv; |
69 | s32 temp; |
70 | |
71 | if (lm->i2c == NULL) |
72 | return -ENODEV; |
73 | |
74 | #define LM87_INT_TEMP 0x27 |
75 | |
76 | /* Read temperature register */ |
77 | temp = wf_lm87_read_reg(chip: lm->i2c, LM87_INT_TEMP); |
78 | if (temp < 0) |
79 | return temp; |
80 | *value = temp << 16; |
81 | |
82 | return 0; |
83 | } |
84 | |
85 | static void wf_lm87_release(struct wf_sensor *sr) |
86 | { |
87 | struct wf_lm87_sensor *lm = wf_to_lm87(sr); |
88 | |
89 | kfree(objp: lm); |
90 | } |
91 | |
92 | static const struct wf_sensor_ops wf_lm87_ops = { |
93 | .get_value = wf_lm87_get, |
94 | .release = wf_lm87_release, |
95 | .owner = THIS_MODULE, |
96 | }; |
97 | |
98 | static int wf_lm87_probe(struct i2c_client *client) |
99 | { |
100 | struct wf_lm87_sensor *lm; |
101 | const char *name = NULL, *loc; |
102 | struct device_node *np = NULL; |
103 | int rc; |
104 | |
105 | /* |
106 | * The lm87 contains a whole pile of sensors, additionally, |
107 | * the Xserve G5 has several lm87's. However, for now we only |
108 | * care about the internal temperature sensor |
109 | */ |
110 | for_each_child_of_node(client->dev.of_node, np) { |
111 | if (!of_node_name_eq(np, name: "int-temp" )) |
112 | continue; |
113 | loc = of_get_property(node: np, name: "location" , NULL); |
114 | if (!loc) |
115 | continue; |
116 | if (strstr(loc, "DIMM" )) |
117 | name = "dimms-temp" ; |
118 | else if (strstr(loc, "Processors" )) |
119 | name = "between-cpus-temp" ; |
120 | if (name) { |
121 | of_node_put(node: np); |
122 | break; |
123 | } |
124 | } |
125 | if (!name) { |
126 | pr_warn("wf_lm87: Unsupported sensor %pOF\n" , |
127 | client->dev.of_node); |
128 | return -ENODEV; |
129 | } |
130 | |
131 | lm = kzalloc(size: sizeof(struct wf_lm87_sensor), GFP_KERNEL); |
132 | if (lm == NULL) |
133 | return -ENODEV; |
134 | |
135 | lm->i2c = client; |
136 | lm->sens.name = name; |
137 | lm->sens.ops = &wf_lm87_ops; |
138 | lm->sens.priv = lm; |
139 | i2c_set_clientdata(client, data: lm); |
140 | |
141 | rc = wf_register_sensor(sr: &lm->sens); |
142 | if (rc) |
143 | kfree(objp: lm); |
144 | return rc; |
145 | } |
146 | |
147 | static void wf_lm87_remove(struct i2c_client *client) |
148 | { |
149 | struct wf_lm87_sensor *lm = i2c_get_clientdata(client); |
150 | |
151 | /* Mark client detached */ |
152 | lm->i2c = NULL; |
153 | |
154 | /* release sensor */ |
155 | wf_unregister_sensor(sr: &lm->sens); |
156 | } |
157 | |
158 | static const struct i2c_device_id wf_lm87_id[] = { |
159 | { "MAC,lm87cimt" , 0 }, |
160 | { } |
161 | }; |
162 | MODULE_DEVICE_TABLE(i2c, wf_lm87_id); |
163 | |
164 | static const struct of_device_id wf_lm87_of_id[] = { |
165 | { .compatible = "lm87cimt" , }, |
166 | { } |
167 | }; |
168 | MODULE_DEVICE_TABLE(of, wf_lm87_of_id); |
169 | |
170 | static struct i2c_driver wf_lm87_driver = { |
171 | .driver = { |
172 | .name = "wf_lm87" , |
173 | .of_match_table = wf_lm87_of_id, |
174 | }, |
175 | .probe = wf_lm87_probe, |
176 | .remove = wf_lm87_remove, |
177 | .id_table = wf_lm87_id, |
178 | }; |
179 | |
180 | static int __init wf_lm87_sensor_init(void) |
181 | { |
182 | /* We only support this on the Xserve */ |
183 | if (!of_machine_is_compatible(compat: "RackMac3,1" )) |
184 | return -ENODEV; |
185 | |
186 | return i2c_add_driver(&wf_lm87_driver); |
187 | } |
188 | |
189 | static void __exit wf_lm87_sensor_exit(void) |
190 | { |
191 | i2c_del_driver(driver: &wf_lm87_driver); |
192 | } |
193 | |
194 | |
195 | module_init(wf_lm87_sensor_init); |
196 | module_exit(wf_lm87_sensor_exit); |
197 | |
198 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>" ); |
199 | MODULE_DESCRIPTION("LM87 sensor objects for PowerMacs thermal control" ); |
200 | MODULE_LICENSE("GPL" ); |
201 | |
202 | |