xref: /openbsd-src/sys/dev/fdt/amlpwm.c (revision 9fdf0c627b1fec102f212f847a6f7676c1829e65)
1*9fdf0c62Smpi /*	$OpenBSD: amlpwm.c,v 1.3 2021/10/24 17:52:26 mpi Exp $	*/
283338f56Skettenis /*
383338f56Skettenis  * Copyright (c) 2019 Mark Kettenis <kettenis@openbsd.org>
483338f56Skettenis  *
583338f56Skettenis  * Permission to use, copy, modify, and distribute this software for any
683338f56Skettenis  * purpose with or without fee is hereby granted, provided that the above
783338f56Skettenis  * copyright notice and this permission notice appear in all copies.
883338f56Skettenis  *
983338f56Skettenis  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1083338f56Skettenis  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1183338f56Skettenis  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1283338f56Skettenis  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1383338f56Skettenis  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1483338f56Skettenis  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1583338f56Skettenis  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1683338f56Skettenis  */
1783338f56Skettenis 
1883338f56Skettenis #include <sys/param.h>
1983338f56Skettenis #include <sys/systm.h>
2083338f56Skettenis #include <sys/device.h>
2183338f56Skettenis 
2283338f56Skettenis #include <machine/intr.h>
2383338f56Skettenis #include <machine/bus.h>
2483338f56Skettenis #include <machine/fdt.h>
2583338f56Skettenis 
2683338f56Skettenis #include <dev/ofw/openfirm.h>
2783338f56Skettenis #include <dev/ofw/ofw_clock.h>
2883338f56Skettenis #include <dev/ofw/ofw_misc.h>
292e29bb8bSkettenis #include <dev/ofw/ofw_pinctrl.h>
3083338f56Skettenis #include <dev/ofw/fdt.h>
3183338f56Skettenis 
3283338f56Skettenis #define PWM_PWM_A		0x00
3383338f56Skettenis #define PWM_PWM_B		0x01
3483338f56Skettenis #define  PWM_PWM_HIGH(x)	((x) >> 16)
3583338f56Skettenis #define  PWM_PWM_HIGH_SHIFT	16
3683338f56Skettenis #define  PWM_PWM_LOW(x)		((x) & 0xffff)
3783338f56Skettenis #define  PWM_PWM_LOW_SHIFT	0
3883338f56Skettenis #define PWM_MISC_REG_AB		0x02
3983338f56Skettenis #define  PWM_B_CLK_EN		(1 << 23)
4083338f56Skettenis #define  PWM_B_CLK_DIV_MASK	(0x7f << 16)
4183338f56Skettenis #define  PWM_B_CLK_DIV_SHIFT	16
4283338f56Skettenis #define  PWM_B_CLK_DIV(x)	((((x) >> 16) & 0x7f) + 1)
4383338f56Skettenis #define  PWM_A_CLK_EN		(1 << 15)
4483338f56Skettenis #define  PWM_A_CLK_DIV_MASK	(0x7f << 8)
4583338f56Skettenis #define  PWM_A_CLK_DIV_SHIFT	8
4683338f56Skettenis #define  PWM_A_CLK_DIV(x)	((((x) >> 8) & 0x7f) + 1)
4783338f56Skettenis #define  PWM_B_EN		(1 << 1)
4883338f56Skettenis #define  PWM_A_EN		(1 << 0)
4983338f56Skettenis 
5083338f56Skettenis #define HREAD4(sc, reg)							\
5183338f56Skettenis 	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg) << 2))
5283338f56Skettenis #define HWRITE4(sc, reg, val)						\
5383338f56Skettenis 	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg) << 2, (val))
5483338f56Skettenis #define HSET4(sc, reg, bits)						\
5583338f56Skettenis 	HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
5683338f56Skettenis #define HCLR4(sc, reg, bits)						\
5783338f56Skettenis 	HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
5883338f56Skettenis 
5983338f56Skettenis struct amlpwm_softc {
6083338f56Skettenis 	struct device		sc_dev;
6183338f56Skettenis 	bus_space_tag_t		sc_iot;
6283338f56Skettenis 	bus_space_handle_t	sc_ioh;
6383338f56Skettenis 
6483338f56Skettenis 	uint32_t		sc_clkin[2];
6583338f56Skettenis 
6683338f56Skettenis 	struct pwm_device	sc_pd;
6783338f56Skettenis };
6883338f56Skettenis 
6983338f56Skettenis int amlpwm_match(struct device *, void *, void *);
7083338f56Skettenis void amlpwm_attach(struct device *, struct device *, void *);
7183338f56Skettenis 
72*9fdf0c62Smpi const struct cfattach	amlpwm_ca = {
7383338f56Skettenis 	sizeof (struct amlpwm_softc), amlpwm_match, amlpwm_attach
7483338f56Skettenis };
7583338f56Skettenis 
7683338f56Skettenis struct cfdriver amlpwm_cd = {
7783338f56Skettenis 	NULL, "amlpwm", DV_DULL
7883338f56Skettenis };
7983338f56Skettenis 
8083338f56Skettenis int	amlpwm_get_state(void *, uint32_t *, struct pwm_state *);
8183338f56Skettenis int	amlpwm_set_state(void *, uint32_t *, struct pwm_state *);
8283338f56Skettenis 
8383338f56Skettenis int
amlpwm_match(struct device * parent,void * match,void * aux)8483338f56Skettenis amlpwm_match(struct device *parent, void *match, void *aux)
8583338f56Skettenis {
8683338f56Skettenis 	struct fdt_attach_args *faa = aux;
8783338f56Skettenis 	int node = faa->fa_node;
8883338f56Skettenis 
8983338f56Skettenis 	return (OF_is_compatible(node, "amlogic,meson-g12a-ao-pwm-cd") ||
9083338f56Skettenis 	    OF_is_compatible(node, "amlogic,meson-g12a-ee-pwm"));
9183338f56Skettenis }
9283338f56Skettenis 
9383338f56Skettenis void
amlpwm_attach(struct device * parent,struct device * self,void * aux)9483338f56Skettenis amlpwm_attach(struct device *parent, struct device *self, void *aux)
9583338f56Skettenis {
9683338f56Skettenis 	struct amlpwm_softc *sc = (struct amlpwm_softc *)self;
9783338f56Skettenis 	struct fdt_attach_args *faa = aux;
9883338f56Skettenis 
9983338f56Skettenis 	if (faa->fa_nreg < 1) {
10083338f56Skettenis 		printf(": no registers\n");
10183338f56Skettenis 		return;
10283338f56Skettenis 	}
10383338f56Skettenis 
10483338f56Skettenis 	sc->sc_iot = faa->fa_iot;
10583338f56Skettenis 	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
10683338f56Skettenis 	    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
10783338f56Skettenis 		printf(": can't map registers\n");
10883338f56Skettenis 		return;
10983338f56Skettenis 	}
11083338f56Skettenis 
11183338f56Skettenis 	sc->sc_clkin[0] = clock_get_frequency(faa->fa_node, "clkin0");
11283338f56Skettenis 	sc->sc_clkin[1] = clock_get_frequency(faa->fa_node, "clkin1");
11383338f56Skettenis 
11483338f56Skettenis 	printf("\n");
11583338f56Skettenis 
1162e29bb8bSkettenis 	pinctrl_byname(faa->fa_node, "default");
1172e29bb8bSkettenis 
11883338f56Skettenis 	sc->sc_pd.pd_node = faa->fa_node;
11983338f56Skettenis 	sc->sc_pd.pd_cookie = sc;
12083338f56Skettenis 	sc->sc_pd.pd_get_state = amlpwm_get_state;
12183338f56Skettenis 	sc->sc_pd.pd_set_state = amlpwm_set_state;
12283338f56Skettenis 	pwm_register(&sc->sc_pd);
12383338f56Skettenis }
12483338f56Skettenis 
12583338f56Skettenis static inline uint32_t
cycles_to_ns(uint64_t clk_freq,uint32_t clk_div,uint32_t cycles)12683338f56Skettenis cycles_to_ns(uint64_t clk_freq, uint32_t clk_div, uint32_t cycles)
12783338f56Skettenis {
12883338f56Skettenis 	return cycles * clk_div * 1000000000ULL / clk_freq;
12983338f56Skettenis }
13083338f56Skettenis 
13183338f56Skettenis static inline uint32_t
ns_to_cycles(uint64_t clk_freq,uint32_t clk_div,uint32_t ns)13283338f56Skettenis ns_to_cycles(uint64_t clk_freq, uint32_t clk_div, uint32_t ns)
13383338f56Skettenis {
13483338f56Skettenis 	return ns * clk_freq / (clk_div * 1000000000ULL);
13583338f56Skettenis }
13683338f56Skettenis 
13783338f56Skettenis int
amlpwm_get_state(void * cookie,uint32_t * cells,struct pwm_state * ps)13883338f56Skettenis amlpwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps)
13983338f56Skettenis {
14083338f56Skettenis 	struct amlpwm_softc *sc = cookie;
14183338f56Skettenis 	uint32_t idx = cells[0];
14283338f56Skettenis 	uint32_t pwm, misc;
14383338f56Skettenis 	uint32_t total, high;
14483338f56Skettenis 	uint32_t clk_div;
14583338f56Skettenis 	int enabled = 0;
14683338f56Skettenis 
14783338f56Skettenis 	if (idx > 1 || sc->sc_clkin[idx] == 0)
14883338f56Skettenis 		return EINVAL;
14983338f56Skettenis 
15083338f56Skettenis 	pwm = HREAD4(sc, idx == 0 ? PWM_PWM_A : PWM_PWM_B);
15183338f56Skettenis 	misc = HREAD4(sc, PWM_MISC_REG_AB);
15283338f56Skettenis 
15383338f56Skettenis 	if (idx == 0) {
15483338f56Skettenis 		if ((misc & PWM_A_CLK_EN) && (misc & PWM_A_EN))
15583338f56Skettenis 			enabled = 1;
15683338f56Skettenis 		clk_div = PWM_A_CLK_DIV(misc);
15783338f56Skettenis 	} else {
15883338f56Skettenis 		if ((misc & PWM_B_CLK_EN) && (misc & PWM_B_EN))
15983338f56Skettenis 			enabled = 1;
16083338f56Skettenis 		clk_div = PWM_B_CLK_DIV(misc);
16183338f56Skettenis 	}
16283338f56Skettenis 
16383338f56Skettenis 	total = PWM_PWM_LOW(pwm) + PWM_PWM_HIGH(pwm);
16483338f56Skettenis 	high = PWM_PWM_HIGH(pwm);
16583338f56Skettenis 
16683338f56Skettenis 	memset(ps, 0, sizeof(struct pwm_state));
16783338f56Skettenis 	ps->ps_period = cycles_to_ns(sc->sc_clkin[idx], clk_div, total);
16883338f56Skettenis 	ps->ps_pulse_width = cycles_to_ns(sc->sc_clkin[idx], clk_div, high);
16983338f56Skettenis 	ps->ps_enabled = enabled;
17083338f56Skettenis 
17183338f56Skettenis 	return 0;
17283338f56Skettenis }
17383338f56Skettenis 
17483338f56Skettenis int
amlpwm_set_state(void * cookie,uint32_t * cells,struct pwm_state * ps)17583338f56Skettenis amlpwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps)
17683338f56Skettenis {
17783338f56Skettenis 	struct amlpwm_softc *sc = cookie;
17883338f56Skettenis 	uint32_t idx = cells[0];
17983338f56Skettenis 	uint32_t pwm, misc;
18083338f56Skettenis 	uint32_t total, high, low;
18183338f56Skettenis 	uint32_t clk_div = 1;
18283338f56Skettenis 
18383338f56Skettenis 	if (idx > 1 || sc->sc_clkin[idx] == 0)
18483338f56Skettenis 		return EINVAL;
18583338f56Skettenis 
18683338f56Skettenis 	/* Hardware doesn't support polarity inversion. */
18783338f56Skettenis 	if (ps->ps_flags & PWM_POLARITY_INVERTED)
18883338f56Skettenis 		return EINVAL;
18983338f56Skettenis 
19083338f56Skettenis 	if (!ps->ps_enabled) {
19183338f56Skettenis 		HCLR4(sc, PWM_MISC_REG_AB, (idx == 0) ? PWM_A_EN : PWM_B_EN);
19283338f56Skettenis 		return 0;
19383338f56Skettenis 	}
19483338f56Skettenis 
19583338f56Skettenis 	total = ns_to_cycles(sc->sc_clkin[idx], clk_div, ps->ps_period);
19683338f56Skettenis 	while ((total / clk_div) > 0xffff)
19783338f56Skettenis 		clk_div++;
19883338f56Skettenis 	if (clk_div > 128)
19983338f56Skettenis 		return EINVAL;
20083338f56Skettenis 
20183338f56Skettenis 	total = ns_to_cycles(sc->sc_clkin[idx], clk_div, ps->ps_period);
20283338f56Skettenis 	high = ns_to_cycles(sc->sc_clkin[idx], clk_div, ps->ps_pulse_width);
20383338f56Skettenis 	low = total - high;
20483338f56Skettenis 
20583338f56Skettenis 	pwm = (high << PWM_PWM_HIGH_SHIFT) | (low << PWM_PWM_LOW_SHIFT);
20683338f56Skettenis 	misc = HREAD4(sc, PWM_MISC_REG_AB);
20783338f56Skettenis 
20883338f56Skettenis 	if (idx == 0) {
20983338f56Skettenis 		misc &= ~PWM_A_CLK_DIV_MASK;
21083338f56Skettenis 		misc |= (clk_div - 1) << PWM_A_CLK_DIV_SHIFT;
21183338f56Skettenis 		misc |= PWM_A_CLK_EN;
21283338f56Skettenis 	} else {
21383338f56Skettenis 		misc &= ~PWM_B_CLK_DIV_MASK;
21483338f56Skettenis 		misc |= (clk_div - 1) << PWM_B_CLK_DIV_SHIFT;
21583338f56Skettenis 		misc |= PWM_B_CLK_EN;
21683338f56Skettenis 	}
21783338f56Skettenis 
21883338f56Skettenis 	HWRITE4(sc, PWM_MISC_REG_AB, misc);
21983338f56Skettenis 	HWRITE4(sc, (idx == 0) ? PWM_PWM_A : PWM_PWM_B, pwm);
22083338f56Skettenis 	HSET4(sc, PWM_MISC_REG_AB, (idx == 0) ? PWM_A_EN : PWM_B_EN);
22183338f56Skettenis 
22283338f56Skettenis 	return 0;
22383338f56Skettenis }
224