1222686Sandreast/*-
2222686Sandreast * Copyright (c) 2011 Justin Hibbits
3222686Sandreast * Copyright (c) 2010 Andreas Tobler
4222686Sandreast * All rights reserved.
5222686Sandreast *
6222686Sandreast * Redistribution and use in source and binary forms, with or without
7222686Sandreast * modification, are permitted provided that the following conditions
8222686Sandreast * are met:
9222686Sandreast * 1. Redistributions of source code must retain the above copyright
10222686Sandreast *    notice, this list of conditions and the following disclaimer.
11222686Sandreast * 2. Redistributions in binary form must reproduce the above copyright
12222686Sandreast *    notice, this list of conditions and the following disclaimer in the
13222686Sandreast *    documentation and/or other materials provided with the distribution.
14222686Sandreast *
15222686Sandreast * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16222686Sandreast * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17222686Sandreast * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18222686Sandreast * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19222686Sandreast * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20222686Sandreast * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21222686Sandreast * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22222686Sandreast * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23222686Sandreast * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24222686Sandreast * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25222686Sandreast * SUCH DAMAGE.
26222686Sandreast */
27222686Sandreast
28222686Sandreast#include <sys/cdefs.h>
29222686Sandreast__FBSDID("$FreeBSD$");
30222686Sandreast
31222686Sandreast#include <sys/param.h>
32222686Sandreast#include <sys/bus.h>
33222686Sandreast#include <sys/systm.h>
34222686Sandreast#include <sys/module.h>
35222686Sandreast#include <sys/callout.h>
36222686Sandreast#include <sys/conf.h>
37222686Sandreast#include <sys/cpu.h>
38222686Sandreast#include <sys/ctype.h>
39222686Sandreast#include <sys/kernel.h>
40222686Sandreast#include <sys/kthread.h>
41222686Sandreast#include <sys/limits.h>
42222686Sandreast#include <sys/reboot.h>
43222686Sandreast#include <sys/rman.h>
44222686Sandreast#include <sys/sysctl.h>
45222686Sandreast#include <sys/unistd.h>
46222686Sandreast
47222686Sandreast#include <machine/bus.h>
48222686Sandreast#include <machine/md_var.h>
49222686Sandreast
50222686Sandreast#include <dev/iicbus/iicbus.h>
51222686Sandreast#include <dev/iicbus/iiconf.h>
52222686Sandreast
53222686Sandreast#include <dev/ofw/openfirm.h>
54222686Sandreast#include <dev/ofw/ofw_bus.h>
55222686Sandreast#include <powerpc/powermac/powermac_thermal.h>
56222686Sandreast
57222686Sandreaststruct adm1030_softc {
58222686Sandreast	struct pmac_fan fan;
59222686Sandreast	device_t	sc_dev;
60222686Sandreast	struct intr_config_hook enum_hook;
61222686Sandreast	uint32_t	sc_addr;
62230035Sjhibbits	int		sc_pwm;
63222686Sandreast};
64222686Sandreast
65222686Sandreast/* Regular bus attachment functions */
66222686Sandreaststatic int	adm1030_probe(device_t);
67222686Sandreaststatic int	adm1030_attach(device_t);
68222686Sandreast
69222686Sandreast/* Utility functions */
70222686Sandreaststatic void	adm1030_start(void *xdev);
71222686Sandreaststatic int	adm1030_write_byte(device_t dev, uint32_t addr, uint8_t reg, uint8_t buf);
72230035Sjhibbitsstatic int	adm1030_set(struct adm1030_softc *fan, int pwm);
73230035Sjhibbitsstatic int	adm1030_sysctl(SYSCTL_HANDLER_ARGS);
74222686Sandreast
75222686Sandreaststatic device_method_t adm1030_methods[] = {
76222686Sandreast	/* Device interface */
77222686Sandreast	DEVMETHOD(device_probe, adm1030_probe),
78222686Sandreast	DEVMETHOD(device_attach, adm1030_attach),
79222686Sandreast	{0, 0},
80222686Sandreast};
81222686Sandreast
82222686Sandreaststatic driver_t	adm1030_driver = {
83222686Sandreast	"adm1030",
84222686Sandreast	adm1030_methods,
85222686Sandreast	sizeof(struct adm1030_softc)
86222686Sandreast};
87222686Sandreast
88222686Sandreaststatic devclass_t adm1030_devclass;
89222686Sandreast
90222686SandreastDRIVER_MODULE(adm1030, iicbus, adm1030_driver, adm1030_devclass, 0, 0);
91222686Sandreast
92222686Sandreaststatic int
93222686Sandreastadm1030_write_byte(device_t dev, uint32_t addr, uint8_t reg, uint8_t byte)
94222686Sandreast{
95222686Sandreast	unsigned char	buf[4];
96222686Sandreast	int try = 0;
97222686Sandreast
98222686Sandreast	struct iic_msg	msg[] = {
99222686Sandreast		{addr, IIC_M_WR, 0, buf}
100222686Sandreast	};
101222686Sandreast
102222686Sandreast	msg[0].len = 2;
103222686Sandreast	buf[0] = reg;
104222686Sandreast	buf[1] = byte;
105222686Sandreast
106222686Sandreast	for (;;)
107222686Sandreast	{
108222686Sandreast		if (iicbus_transfer(dev, msg, 1) == 0)
109222686Sandreast			return (0);
110222686Sandreast
111222686Sandreast		if (++try > 5) {
112222686Sandreast			device_printf(dev, "iicbus write failed\n");
113222686Sandreast			return (-1);
114222686Sandreast		}
115222686Sandreast		pause("adm1030_write_byte", hz);
116222686Sandreast	}
117222686Sandreast}
118222686Sandreast
119222686Sandreaststatic int
120222686Sandreastadm1030_probe(device_t dev)
121222686Sandreast{
122222686Sandreast	const char     *name, *compatible;
123222686Sandreast	struct adm1030_softc *sc;
124222686Sandreast	phandle_t	handle;
125222686Sandreast	phandle_t	thermostat;
126222686Sandreast
127222686Sandreast	name = ofw_bus_get_name(dev);
128222686Sandreast	compatible = ofw_bus_get_compat(dev);
129222686Sandreast	handle = ofw_bus_get_node(dev);
130222686Sandreast
131222686Sandreast	if (!name)
132222686Sandreast		return (ENXIO);
133222686Sandreast
134222686Sandreast	if (strcmp(name, "fan") != 0 || strcmp(compatible, "adm1030") != 0)
135222686Sandreast		return (ENXIO);
136222686Sandreast
137222686Sandreast	/* This driver can only be used if there's an associated temp sensor. */
138222686Sandreast	if (OF_getprop(handle, "platform-getTemp", &thermostat, sizeof(thermostat)) < 0)
139222686Sandreast		return (ENXIO);
140222686Sandreast
141222686Sandreast	sc = device_get_softc(dev);
142222686Sandreast	sc->sc_dev = dev;
143222686Sandreast	sc->sc_addr = iicbus_get_addr(dev);
144222686Sandreast
145222686Sandreast	device_set_desc(dev, "G4 MDD Fan driver");
146222686Sandreast
147222686Sandreast	return (0);
148222686Sandreast}
149222686Sandreast
150222686Sandreaststatic int
151222686Sandreastadm1030_attach(device_t dev)
152222686Sandreast{
153222686Sandreast	struct adm1030_softc *sc;
154230035Sjhibbits	struct sysctl_ctx_list *ctx;
155230035Sjhibbits	struct sysctl_oid *tree;
156222686Sandreast
157222686Sandreast	sc = device_get_softc(dev);
158222686Sandreast
159222686Sandreast	sc->enum_hook.ich_func = adm1030_start;
160222686Sandreast	sc->enum_hook.ich_arg = dev;
161222686Sandreast
162222686Sandreast	/*
163230035Sjhibbits	 * Wait until interrupts are available, which won't be until the openpic is
164230035Sjhibbits	 * intialized.
165222686Sandreast	 */
166222686Sandreast
167222686Sandreast	if (config_intrhook_establish(&sc->enum_hook) != 0)
168222686Sandreast		return (ENOMEM);
169222686Sandreast
170230035Sjhibbits	ctx = device_get_sysctl_ctx(dev);
171230035Sjhibbits	tree = device_get_sysctl_tree(dev);
172230035Sjhibbits	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "pwm",
173230035Sjhibbits			CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev,
174230035Sjhibbits			0, adm1030_sysctl, "I", "Fan PWM Rate");
175230035Sjhibbits
176222686Sandreast	return (0);
177222686Sandreast}
178222686Sandreast
179222686Sandreaststatic void
180222686Sandreastadm1030_start(void *xdev)
181222686Sandreast{
182222686Sandreast	struct adm1030_softc *sc;
183222686Sandreast
184222686Sandreast	device_t	dev = (device_t) xdev;
185222686Sandreast
186222686Sandreast	sc = device_get_softc(dev);
187222686Sandreast
188222686Sandreast	/* Start the adm1030 device. */
189222686Sandreast	adm1030_write_byte(sc->sc_dev, sc->sc_addr, 0x1, 0x1);
190222686Sandreast	adm1030_write_byte(sc->sc_dev, sc->sc_addr, 0x0, 0x95);
191222686Sandreast	adm1030_write_byte(sc->sc_dev, sc->sc_addr, 0x23, 0x91);
192222686Sandreast
193222686Sandreast	/* Use the RPM fields as PWM duty cycles. */
194222686Sandreast	sc->fan.min_rpm = 0;
195230035Sjhibbits	sc->fan.max_rpm = 0x0F;
196222686Sandreast	sc->fan.default_rpm = 2;
197222686Sandreast
198222686Sandreast	strcpy(sc->fan.name, "MDD Case fan");
199222686Sandreast	sc->fan.zone = 0;
200222686Sandreast	sc->fan.read = NULL;
201222686Sandreast	sc->fan.set = (int (*)(struct pmac_fan *, int))adm1030_set;
202222686Sandreast	config_intrhook_disestablish(&sc->enum_hook);
203222686Sandreast
204222686Sandreast	pmac_thermal_fan_register(&sc->fan);
205222686Sandreast}
206222686Sandreast
207222686Sandreaststatic int adm1030_set(struct adm1030_softc *fan, int pwm)
208222686Sandreast{
209222686Sandreast	/* Clamp the PWM to 0-0xF, one nibble. */
210222686Sandreast	if (pwm > 0xF)
211222686Sandreast		pwm = 0xF;
212222686Sandreast	if (pwm < 0)
213222686Sandreast		pwm = 0;
214222686Sandreast
215222686Sandreast	if (adm1030_write_byte(fan->sc_dev, fan->sc_addr, 0x22, pwm) < 0)
216222686Sandreast		return (-1);
217222686Sandreast
218230035Sjhibbits	fan->sc_pwm = pwm;
219222686Sandreast	return (0);
220222686Sandreast}
221222686Sandreast
222230035Sjhibbitsstatic int
223230035Sjhibbitsadm1030_sysctl(SYSCTL_HANDLER_ARGS)
224230035Sjhibbits{
225230035Sjhibbits	device_t adm1030;
226230035Sjhibbits	struct adm1030_softc *sc;
227230035Sjhibbits	int pwm, error;
228230035Sjhibbits
229230035Sjhibbits	adm1030 = arg1;
230230035Sjhibbits	sc = device_get_softc(adm1030);
231230035Sjhibbits
232230035Sjhibbits	pwm = sc->sc_pwm;
233230035Sjhibbits
234230035Sjhibbits	error = sysctl_handle_int(oidp, &pwm, 0, req);
235230035Sjhibbits
236230035Sjhibbits	if (error || !req->newptr)
237230035Sjhibbits		return (error);
238230035Sjhibbits
239230035Sjhibbits	return (adm1030_set(sc, pwm));
240230035Sjhibbits}
241