77a119553f9e387f5f8849f8dc12876c53f59db9
[dragonfly.git] / sys / dev / powermng / wbsio / wbsio.c
1 /*      $NetBSD: wbsio.c,v 1.1 2010/02/21 05:16:29 cnst Exp $   */
2 /*      $OpenBSD: wbsio.c,v 1.5 2009/03/29 21:53:52 sthen Exp $ */
3 /*
4  * Copyright (c) 2008 Mark Kettenis <kettenis@openbsd.org>
5  * Copyright (c) 2010 Constantine A. Murenin <cnst++@dragonflybsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19
20 /*
21  * Winbond LPC Super I/O driver.
22  */
23
24 #include <sys/param.h>
25 #include <sys/bus.h>
26 #include <sys/kernel.h>
27 #include <sys/module.h>
28 #include <sys/rman.h>
29 #include <sys/systm.h>
30
31 #include <bus/isa/isavar.h>
32
33 /* ISA bus registers */
34 #define WBSIO_INDEX             0x00    /* Configuration Index Register */
35 #define WBSIO_DATA              0x01    /* Configuration Data Register */
36
37 #define WBSIO_IOSIZE            0x02    /* ISA I/O space size */
38
39 #define WBSIO_CONF_EN_MAGIC     0x87    /* enable configuration mode */
40 #define WBSIO_CONF_DS_MAGIC     0xaa    /* disable configuration mode */
41
42 /* Configuration Space Registers */
43 #define WBSIO_LDN               0x07    /* Logical Device Number */
44 #define WBSIO_ID                0x20    /* Device ID */
45 #define WBSIO_REV               0x21    /* Device Revision */
46
47 #define WBSIO_ID_W83627HF       0x52
48 #define WBSIO_ID_W83627THF      0x82
49 #define WBSIO_ID_W83627EHF      0x88
50 #define WBSIO_ID_W83627DHG      0xa0
51 #define WBSIO_ID_W83627SF       0x59
52 #define WBSIO_ID_W83637HF       0x70
53 #define WBSIO_ID_W83667HG       0xa5
54 #define WBSIO_ID_W83697HF       0x60
55
56 /* Logical Device Number (LDN) Assignments */
57 #define WBSIO_LDN_HM            0x0b
58
59 /* Hardware Monitor Control Registers (LDN B) */
60 #define WBSIO_HM_ADDR_MSB       0x60    /* Address [15:8] */
61 #define WBSIO_HM_ADDR_LSB       0x61    /* Address [7:0] */
62
63 struct wbsio_softc {
64         struct device           *sc_dev;
65
66         struct resource         *sc_iores;
67         int                     sc_iorid;
68
69         bus_space_tag_t         sc_iot;
70         bus_space_handle_t      sc_ioh;
71 };
72
73 static void     wbsio_identify(driver_t *, struct device *);
74 static int      wbsio_probe(struct device *);
75 static int      wbsio_attach(struct device *);
76 static int      wbsio_detach(struct device *);
77
78 static device_method_t wbsio_methods[] = {
79         DEVMETHOD(device_identify,      wbsio_identify),
80         DEVMETHOD(device_probe,         wbsio_probe),
81         DEVMETHOD(device_attach,        wbsio_attach),
82         DEVMETHOD(device_detach,        wbsio_detach),
83
84         { NULL, NULL}
85 };
86
87 static driver_t wbsio_driver = {
88         "wbsio",
89         wbsio_methods,
90         sizeof(struct wbsio_softc)
91 };
92
93 static devclass_t wbsio_devclass;
94
95 DRIVER_MODULE(wbsio, isa, wbsio_driver, wbsio_devclass, NULL, NULL);
96
97
98 static __inline void
99 wbsio_conf_enable(bus_space_tag_t iot, bus_space_handle_t ioh)
100 {
101         bus_space_write_1(iot, ioh, WBSIO_INDEX, WBSIO_CONF_EN_MAGIC);
102         bus_space_write_1(iot, ioh, WBSIO_INDEX, WBSIO_CONF_EN_MAGIC);
103 }
104
105 static __inline void
106 wbsio_conf_disable(bus_space_tag_t iot, bus_space_handle_t ioh)
107 {
108         bus_space_write_1(iot, ioh, WBSIO_INDEX, WBSIO_CONF_DS_MAGIC);
109 }
110
111 static __inline u_int8_t
112 wbsio_conf_read(bus_space_tag_t iot, bus_space_handle_t ioh, u_int8_t index)
113 {
114         bus_space_write_1(iot, ioh, WBSIO_INDEX, index);
115         return (bus_space_read_1(iot, ioh, WBSIO_DATA));
116 }
117
118 static __inline void
119 wbsio_conf_write(bus_space_tag_t iot, bus_space_handle_t ioh, u_int8_t index,
120     u_int8_t data)
121 {
122         bus_space_write_1(iot, ioh, WBSIO_INDEX, index);
123         bus_space_write_1(iot, ioh, WBSIO_DATA, data);
124 }
125
126 static void
127 wbsio_identify(driver_t *driver, struct device *parent)
128 {
129 #ifdef KLD_MODULE
130         struct device *child[2];
131         const int port[2] = { 0x2e, 0x4e };
132
133         for (int i = 0; i < 2; i++) {
134                 child[i] = device_find_child(parent, driver->name, i);
135                 if (child[i] == NULL) {
136                         child[i] = BUS_ADD_CHILD(parent, parent, ISA_ORDER_PNP,
137                             driver->name, i);
138                         if (child[i] == NULL) {
139                                 kprintf("%s: cannot add child[%i]\n",
140                                     __func__, i);
141                                 continue;
142                         }
143                 } else
144                         continue;
145                 if (bus_set_resource(child[i], SYS_RES_IOPORT, 0,
146                         port[i], WBSIO_IOSIZE))
147                         kprintf("%s: cannot set resource for child[%i]\n",
148                             __func__, i);
149         }
150 #endif
151 }
152
153 static int
154 wbsio_probe(struct device *dev)
155 {
156         struct resource *iores;
157         int iorid = 0;
158         bus_space_tag_t iot;
159         bus_space_handle_t ioh;
160         uint8_t reg_id, reg_rev;
161         const char *desc = NULL;
162         char fulldesc[64];
163
164         /* Match by device ID */
165
166         iores = bus_alloc_resource(dev, SYS_RES_IOPORT, &iorid,
167             0ul, ~0ul, WBSIO_IOSIZE,
168             RF_ACTIVE);
169         if (iores == NULL)
170                 return ENXIO;
171         iot = rman_get_bustag(iores);
172         ioh = rman_get_bushandle(iores);
173
174         wbsio_conf_enable(iot, ioh);
175         /* Read device ID */
176         reg_id = wbsio_conf_read(iot, ioh, WBSIO_ID);
177         /* Read device revision */
178         reg_rev = wbsio_conf_read(iot, ioh, WBSIO_REV);
179         wbsio_conf_disable(iot, ioh);
180         bus_release_resource(dev, SYS_RES_IOPORT, iorid, iores);
181
182         switch (reg_id) {
183         case WBSIO_ID_W83627HF:
184                 desc = "W83627HF";
185                 break;
186         case WBSIO_ID_W83627THF:
187                 desc = "W83627THF";
188                 break;
189         case WBSIO_ID_W83627EHF:
190                 desc = "W83627EHF";
191                 break;
192         case WBSIO_ID_W83627DHG:
193                 desc = "W83627DHG";
194                 break;
195         case WBSIO_ID_W83637HF:
196                 desc = "W83637HF";
197                 break;
198         case WBSIO_ID_W83667HG:
199                 desc = "W83667HG";
200                 break;
201         case WBSIO_ID_W83697HF:
202                 desc = "W83697HF";
203                 break;
204         }
205
206         if (desc == NULL) {
207 #ifndef KLD_MODULE
208                 if (bootverbose)
209 #endif
210                         if (!(reg_id == 0xff && reg_rev == 0xff))
211                                 device_printf(dev, "%s port 0x%02x: "
212                                     "Device ID 0x%02x, Rev 0x%02x\n",
213                                     __func__, isa_get_port(dev),
214                                     reg_id, reg_rev);
215                 return ENXIO;
216         }
217
218         ksnprintf(fulldesc, sizeof(fulldesc),
219             "Winbond LPC Super I/O %s rev 0x%02x", desc, reg_rev);
220         device_set_desc_copy(dev, fulldesc);
221         return 0;
222 }
223
224 static int
225 wbsio_attach(struct device *dev)
226 {
227         struct wbsio_softc *sc = device_get_softc(dev);
228         uint8_t reg0, reg1;
229         uint16_t iobase;
230         struct device *parent = device_get_parent(dev);
231         struct device *child;
232         struct devclass *c_dc;
233         int c_maxunit;
234
235         /* Map ISA I/O space */
236         sc->sc_iores = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->sc_iorid,
237             0ul, ~0ul, WBSIO_IOSIZE,
238             RF_ACTIVE);
239         if (sc->sc_iores == NULL) {
240                 device_printf(dev, "can't map i/o space\n");
241                 return ENXIO;
242         }
243         sc->sc_iot = rman_get_bustag(sc->sc_iores);
244         sc->sc_ioh = rman_get_bushandle(sc->sc_iores);
245
246         /* Enter configuration mode */
247         wbsio_conf_enable(sc->sc_iot, sc->sc_ioh);
248
249         /* Select HM logical device */
250         wbsio_conf_write(sc->sc_iot, sc->sc_ioh, WBSIO_LDN, WBSIO_LDN_HM);
251
252         /*
253          * The address should be 8-byte aligned, but it seems some
254          * BIOSes ignore this.  They get away with it, because
255          * Apparently the hardware simply ignores the lower three
256          * bits.  We do the same here.
257          */
258         reg0 = wbsio_conf_read(sc->sc_iot, sc->sc_ioh, WBSIO_HM_ADDR_LSB);
259         reg1 = wbsio_conf_read(sc->sc_iot, sc->sc_ioh, WBSIO_HM_ADDR_MSB);
260         iobase = (reg1 << 8) | (reg0 & ~0x7);
261         device_printf(dev, "hardware monitor iobase is 0x%x\n", iobase);
262
263         /* Escape from configuration mode */
264         wbsio_conf_disable(sc->sc_iot, sc->sc_ioh);
265
266         if (iobase == 0) {
267                 device_printf(dev, "no hardware monitor configured\n");
268                 return 0;
269         }
270
271         child = NULL;
272         c_dc = devclass_find("lm");
273         if (c_dc == NULL) {
274                 device_printf(dev, "lm devclass not found\n");
275                 return ENXIO;
276         }
277         c_maxunit = devclass_get_maxunit(c_dc);
278         for (int u = 0; u < c_maxunit; u++) {
279                 child = devclass_get_device(c_dc, u);
280                 if (child == NULL)
281                         continue;
282                 if (isa_get_port(child) == iobase) {
283                         if (device_is_attached(child)) {
284                                 device_printf(dev,
285                                     "%s is already attached at 0x%x\n",
286                                     device_get_nameunit(child), iobase);
287                                 return 0;
288                         }
289                         break;
290                 }
291                 if (device_is_attached(child)) {
292                         child = NULL;
293                         continue;
294                 }
295                 device_printf(dev,
296                     "found unused %s at 0x%x with state %i, reusing at 0x%x\n",
297                     device_get_nameunit(child), isa_get_port(child),
298                     device_get_state(child), iobase);
299                 break;
300         }
301         if (child == NULL)
302                 child = BUS_ADD_CHILD(parent, parent, ISA_ORDER_PNP,
303                     "lm", -1);
304 //      child = BUS_ADD_CHILD(parent, parent, ISA_ORDER_PNP,
305 //          "lm", 3 + device_get_unit(dev));
306         if (child == NULL) {
307                 device_printf(dev, "cannot add child\n");
308                 return ENXIO;
309         }
310         if (bus_set_resource(child, SYS_RES_IOPORT, 0, iobase, 8)) {
311                 device_printf(dev, "cannot set resource\n");
312                 return ENXIO;
313         }
314         return device_probe_and_attach(child);
315 }
316
317 static int
318 wbsio_detach(struct device *dev)
319 {
320         struct wbsio_softc *sc = device_get_softc(dev);
321
322         return bus_release_resource(dev, SYS_RES_IOPORT,
323             sc->sc_iorid, sc->sc_iores);
324 }