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