1/*	$OpenBSD: pwmfan.c,v 1.2 2021/10/24 17:52:26 mpi Exp $	*/
2/*
3 * Copyright (c) 2019 Krystian Lewandowski
4 * Copyright (c) 2019 Mark Kettenis <kettenis@openbsd.org>
5 * Copyright (c) 2019 Patrick Wildt <patrick@blueri.se>
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#include <sys/param.h>
21#include <sys/systm.h>
22#include <sys/device.h>
23#include <sys/malloc.h>
24
25#include <machine/fdt.h>
26#include <machine/bus.h>
27
28#include <dev/ofw/openfirm.h>
29#include <dev/ofw/ofw_gpio.h>
30#include <dev/ofw/ofw_misc.h>
31#include <dev/ofw/ofw_thermal.h>
32
33struct pwmfan_softc {
34	struct device		sc_dev;
35	uint32_t		*sc_pwm;
36	int			sc_pwm_len;
37	uint32_t		*sc_levels;
38	int			sc_nlevels;
39	int			sc_curlevel;
40
41	struct cooling_device	sc_cd;
42};
43
44int	pwmfan_match(struct device *, void *, void *);
45void	pwmfan_attach(struct device *, struct device *, void *);
46
47const struct cfattach pwmfan_ca = {
48	sizeof(struct pwmfan_softc), pwmfan_match, pwmfan_attach
49};
50
51struct cfdriver pwmfan_cd = {
52	NULL, "pwmfan", DV_DULL
53};
54
55uint32_t pwmfan_get_cooling_level(void *, uint32_t *);
56void	pwmfan_set_cooling_level(void *, uint32_t *, uint32_t);
57
58int
59pwmfan_match(struct device *parent, void *match, void *aux)
60{
61	struct fdt_attach_args *faa = aux;
62
63	return OF_is_compatible(faa->fa_node, "pwm-fan");
64}
65
66void
67pwmfan_attach(struct device *parent, struct device *self, void *aux)
68{
69	struct pwmfan_softc *sc = (struct pwmfan_softc *)self;
70	struct fdt_attach_args *faa = aux;
71	int len;
72
73	len = OF_getproplen(faa->fa_node, "pwms");
74	if (len < 0) {
75		printf(": no pwm\n");
76		return;
77	}
78
79	sc->sc_pwm = malloc(len, M_DEVBUF, M_WAITOK);
80	OF_getpropintarray(faa->fa_node, "pwms", sc->sc_pwm, len);
81	sc->sc_pwm_len = len;
82
83	len = OF_getproplen(faa->fa_node, "cooling-levels");
84	if (len < 0) {
85		free(sc->sc_pwm, M_DEVBUF, sc->sc_pwm_len);
86		printf(": no cooling levels\n");
87		return;
88	}
89
90	sc->sc_levels = malloc(len, M_DEVBUF, M_WAITOK);
91	OF_getpropintarray(faa->fa_node, "cooling-levels",
92	    sc->sc_levels, len);
93	sc->sc_nlevels = len / sizeof(uint32_t);
94
95	printf("\n");
96
97	sc->sc_cd.cd_node = faa->fa_node;
98	sc->sc_cd.cd_cookie = sc;
99	sc->sc_cd.cd_get_level = pwmfan_get_cooling_level;
100	sc->sc_cd.cd_set_level = pwmfan_set_cooling_level;
101	cooling_device_register(&sc->sc_cd);
102}
103
104uint32_t
105pwmfan_get_cooling_level(void *cookie, uint32_t *cells)
106{
107	struct pwmfan_softc *sc = cookie;
108
109	return sc->sc_curlevel;
110}
111
112void
113pwmfan_set_cooling_level(void *cookie, uint32_t *cells, uint32_t level)
114{
115	struct pwmfan_softc *sc = cookie;
116	struct pwm_state ps;
117
118	if (level == sc->sc_curlevel || level > sc->sc_nlevels ||
119	    sc->sc_levels[level] > 255)
120		return;
121
122	if (pwm_init_state(sc->sc_pwm, &ps))
123		return;
124
125	sc->sc_curlevel = level;
126	level = sc->sc_levels[level];
127
128	ps.ps_enabled = level ? 1 : 0;
129	ps.ps_pulse_width = (ps.ps_period * level) / 255;
130	pwm_set_state(sc->sc_pwm, &ps);
131}
132