Index: stable/12/sys/arm/allwinner/clkng/aw_clk_nm.c =================================================================== --- stable/12/sys/arm/allwinner/clkng/aw_clk_nm.c (revision 350627) +++ stable/12/sys/arm/allwinner/clkng/aw_clk_nm.c (revision 350628) @@ -1,382 +1,382 @@ /*- * Copyright (c) 2017 Emmanuel Vadot * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include "clkdev_if.h" /* * clknode for clocks matching the formula : * * clk = clkin / n / m * */ struct aw_clk_nm_sc { uint32_t offset; struct aw_clk_factor m; struct aw_clk_factor n; struct aw_clk_factor prediv; struct aw_clk_frac frac; uint32_t mux_shift; uint32_t mux_mask; uint32_t gate_shift; uint32_t lock_shift; uint32_t lock_retries; uint32_t flags; }; #define WRITE4(_clk, off, val) \ CLKDEV_WRITE_4(clknode_get_device(_clk), off, val) #define READ4(_clk, off, val) \ CLKDEV_READ_4(clknode_get_device(_clk), off, val) #define DEVICE_LOCK(_clk) \ CLKDEV_DEVICE_LOCK(clknode_get_device(_clk)) #define DEVICE_UNLOCK(_clk) \ CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk)) static int aw_clk_nm_init(struct clknode *clk, device_t dev) { struct aw_clk_nm_sc *sc; uint32_t val, idx; sc = clknode_get_softc(clk); idx = 0; if ((sc->flags & AW_CLK_HAS_MUX) != 0) { DEVICE_LOCK(clk); READ4(clk, sc->offset, &val); DEVICE_UNLOCK(clk); idx = (val & sc->mux_mask) >> sc->mux_shift; } clknode_init_parent_idx(clk, idx); return (0); } static int aw_clk_nm_set_gate(struct clknode *clk, bool enable) { struct aw_clk_nm_sc *sc; uint32_t val; sc = clknode_get_softc(clk); if ((sc->flags & AW_CLK_HAS_GATE) == 0) return (0); DEVICE_LOCK(clk); READ4(clk, sc->offset, &val); if (enable) val |= (1 << sc->gate_shift); else val &= ~(1 << sc->gate_shift); WRITE4(clk, sc->offset, val); DEVICE_UNLOCK(clk); return (0); } static int aw_clk_nm_set_mux(struct clknode *clk, int index) { struct aw_clk_nm_sc *sc; uint32_t val; sc = clknode_get_softc(clk); if ((sc->flags & AW_CLK_HAS_MUX) == 0) return (0); DEVICE_LOCK(clk); READ4(clk, sc->offset, &val); val &= ~sc->mux_mask; val |= index << sc->mux_shift; WRITE4(clk, sc->offset, val); DEVICE_UNLOCK(clk); return (0); } static uint64_t aw_clk_nm_find_best(struct aw_clk_nm_sc *sc, uint64_t fparent, uint64_t *fout, uint32_t *factor_n, uint32_t *factor_m) { uint64_t cur, best; uint32_t m, n, max_m, max_n, min_m, min_n; *factor_n = *factor_m = 0; max_m = aw_clk_factor_get_max(&sc->m); max_n = aw_clk_factor_get_max(&sc->n); min_m = aw_clk_factor_get_min(&sc->m); min_n = aw_clk_factor_get_min(&sc->n); for (m = min_m; m <= max_m; ) { for (n = min_m; n <= max_n; ) { cur = fparent / n / m; if (abs(*fout - cur) < abs(*fout - best)) { best = cur; *factor_n = n; *factor_m = m; } if ((sc->n.flags & AW_CLK_FACTOR_POWER_OF_TWO) != 0) n <<= 1; else n++; } if ((sc->m.flags & AW_CLK_FACTOR_POWER_OF_TWO) != 0) m <<= 1; else m++; } return (best); } static int aw_clk_nm_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout, int flags, int *stop) { struct aw_clk_nm_sc *sc; struct clknode *p_clk; const char **p_names; uint64_t cur, best, best_frac; uint32_t val, m, n, best_m, best_n; int p_idx, best_parent, retry; sc = clknode_get_softc(clk); best = best_frac = cur = 0; best_parent = 0; if ((sc->flags & AW_CLK_REPARENT) != 0) { p_names = clknode_get_parent_names(clk); for (p_idx = 0; p_idx != clknode_get_parents_num(clk); p_idx++) { p_clk = clknode_find_by_name(p_names[p_idx]); clknode_get_freq(p_clk, &fparent); cur = aw_clk_nm_find_best(sc, fparent, fout, &n, &m); if ((*fout - cur) < (*fout - best)) { best = cur; best_parent = p_idx; best_n = n; best_m = m; } } p_idx = clknode_get_parent_idx(clk); p_clk = clknode_get_parent(clk); clknode_get_freq(p_clk, &fparent); } else { if (sc->flags & AW_CLK_HAS_FRAC && (*fout == sc->frac.freq0 || *fout == sc->frac.freq1)) best = best_frac = *fout; if (best == 0) best = aw_clk_nm_find_best(sc, fparent, fout, &best_n, &best_m); } if ((flags & CLK_SET_DRYRUN) != 0) { *fout = best; *stop = 1; return (0); } if ((best < *fout) && ((flags & CLK_SET_ROUND_DOWN) == 0)) { *stop = 1; return (ERANGE); } if ((best > *fout) && ((flags & CLK_SET_ROUND_UP) == 0)) { *stop = 1; return (ERANGE); } - if (p_idx != best_parent) + if ((sc->flags & AW_CLK_REPARENT) != 0 && p_idx != best_parent) clknode_set_parent_by_idx(clk, best_parent); DEVICE_LOCK(clk); READ4(clk, sc->offset, &val); if (best_frac != 0) { val &= ~sc->frac.mode_sel; if (best_frac == sc->frac.freq0) val &= ~sc->frac.freq_sel; else val |= sc->frac.freq_sel; } else { n = aw_clk_factor_get_value(&sc->n, best_n); m = aw_clk_factor_get_value(&sc->m, best_m); val &= ~sc->n.mask; val &= ~sc->m.mask; val |= n << sc->n.shift; val |= m << sc->m.shift; } WRITE4(clk, sc->offset, val); DEVICE_UNLOCK(clk); if ((sc->flags & AW_CLK_HAS_LOCK) != 0) { for (retry = 0; retry < sc->lock_retries; retry++) { READ4(clk, sc->offset, &val); if ((val & (1 << sc->lock_shift)) != 0) break; DELAY(1000); } } *fout = best; *stop = 1; return (0); } static int aw_clk_nm_recalc(struct clknode *clk, uint64_t *freq) { struct aw_clk_nm_sc *sc; uint32_t val, m, n, prediv; sc = clknode_get_softc(clk); DEVICE_LOCK(clk); READ4(clk, sc->offset, &val); DEVICE_UNLOCK(clk); if (sc->flags & AW_CLK_HAS_FRAC && ((val & sc->frac.mode_sel) == 0)) { if (val & sc->frac.freq_sel) *freq = sc->frac.freq1; else *freq = sc->frac.freq0; } else { m = aw_clk_get_factor(val, &sc->m); n = aw_clk_get_factor(val, &sc->n); if (sc->flags & AW_CLK_HAS_PREDIV) prediv = aw_clk_get_factor(val, &sc->prediv); else prediv = 1; /* For FRAC NM the formula is freq_parent * n / m */ if (sc->flags & AW_CLK_HAS_FRAC) *freq = *freq * n / m; else *freq = *freq / prediv / n / m; } return (0); } static clknode_method_t aw_nm_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_clk_nm_init), CLKNODEMETHOD(clknode_set_gate, aw_clk_nm_set_gate), CLKNODEMETHOD(clknode_set_mux, aw_clk_nm_set_mux), CLKNODEMETHOD(clknode_recalc_freq, aw_clk_nm_recalc), CLKNODEMETHOD(clknode_set_freq, aw_clk_nm_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_nm_clknode, aw_nm_clknode_class, aw_nm_clknode_methods, sizeof(struct aw_clk_nm_sc), clknode_class); int aw_clk_nm_register(struct clkdom *clkdom, struct aw_clk_nm_def *clkdef) { struct clknode *clk; struct aw_clk_nm_sc *sc; clk = clknode_create(clkdom, &aw_nm_clknode_class, &clkdef->clkdef); if (clk == NULL) return (1); sc = clknode_get_softc(clk); sc->offset = clkdef->offset; sc->m.shift = clkdef->m.shift; sc->m.width = clkdef->m.width; sc->m.mask = ((1 << sc->m.width) - 1) << sc->m.shift; sc->m.value = clkdef->m.value; sc->m.flags = clkdef->m.flags; sc->n.shift = clkdef->n.shift; sc->n.width = clkdef->n.width; sc->n.mask = ((1 << sc->n.width) - 1) << sc->n.shift; sc->n.value = clkdef->n.value; sc->n.flags = clkdef->n.flags; sc->prediv.shift = clkdef->prediv.shift; sc->prediv.width = clkdef->prediv.width; sc->prediv.mask = ((1 << sc->prediv.width) - 1) << sc->prediv.shift; sc->prediv.value = clkdef->prediv.value; sc->prediv.flags = clkdef->prediv.flags; sc->prediv.cond_shift = clkdef->prediv.cond_shift; if (clkdef->prediv.cond_width != 0) sc->prediv.cond_mask = ((1 << clkdef->prediv.cond_width) - 1) << sc->prediv.shift; else sc->prediv.cond_mask = clkdef->prediv.cond_mask; sc->prediv.cond_value = clkdef->prediv.cond_value; sc->frac.freq0 = clkdef->frac.freq0; sc->frac.freq1 = clkdef->frac.freq1; sc->frac.mode_sel = 1 << clkdef->frac.mode_sel; sc->frac.freq_sel = 1 << clkdef->frac.freq_sel; sc->mux_shift = clkdef->mux_shift; sc->mux_mask = ((1 << clkdef->mux_width) - 1) << sc->mux_shift; sc->gate_shift = clkdef->gate_shift; sc->lock_shift = clkdef->lock_shift; sc->lock_retries = clkdef->lock_retries; sc->flags = clkdef->flags; clknode_register(clkdom, clk); return (0); } Index: stable/12/sys/arm/allwinner/clkng/aw_clk_prediv_mux.c =================================================================== --- stable/12/sys/arm/allwinner/clkng/aw_clk_prediv_mux.c (revision 350627) +++ stable/12/sys/arm/allwinner/clkng/aw_clk_prediv_mux.c (revision 350628) @@ -1,172 +1,184 @@ /*- * Copyright (c) 2017 Emmanuel Vadot * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include "clkdev_if.h" /* * clknode for clocks matching the formula : * * clk = clkin / prediv / div * * and where prediv is conditional * */ struct aw_clk_prediv_mux_sc { uint32_t offset; uint32_t mux_shift; uint32_t mux_mask; struct aw_clk_factor div; struct aw_clk_factor prediv; uint32_t flags; }; #define WRITE4(_clk, off, val) \ CLKDEV_WRITE_4(clknode_get_device(_clk), off, val) #define READ4(_clk, off, val) \ CLKDEV_READ_4(clknode_get_device(_clk), off, val) #define MODIFY4(_clk, off, clr, set ) \ CLKDEV_MODIFY_4(clknode_get_device(_clk), off, clr, set) #define DEVICE_LOCK(_clk) \ CLKDEV_DEVICE_LOCK(clknode_get_device(_clk)) #define DEVICE_UNLOCK(_clk) \ CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk)) static int aw_clk_prediv_mux_init(struct clknode *clk, device_t dev) { - clknode_init_parent_idx(clk, 0); + struct aw_clk_prediv_mux_sc *sc; + uint32_t val; + + sc = clknode_get_softc(clk); + + DEVICE_LOCK(clk); + READ4(clk, sc->offset, &val); + DEVICE_UNLOCK(clk); + + /* Init the current parent */ + val = (val & sc->mux_mask) >> sc->mux_shift; + clknode_init_parent_idx(clk, val); + return (0); } static int aw_clk_prediv_mux_set_mux(struct clknode *clk, int index) { struct aw_clk_prediv_mux_sc *sc; uint32_t val; sc = clknode_get_softc(clk); DEVICE_LOCK(clk); READ4(clk, sc->offset, &val); val &= ~sc->mux_mask; val |= index << sc->mux_shift; WRITE4(clk, sc->offset, val); DEVICE_UNLOCK(clk); return (0); } static int aw_clk_prediv_mux_recalc(struct clknode *clk, uint64_t *freq) { struct aw_clk_prediv_mux_sc *sc; uint32_t val, div, prediv; sc = clknode_get_softc(clk); DEVICE_LOCK(clk); READ4(clk, sc->offset, &val); DEVICE_UNLOCK(clk); div = aw_clk_get_factor(val, &sc->div); prediv = aw_clk_get_factor(val, &sc->prediv); *freq = *freq / prediv / div; return (0); } static clknode_method_t aw_prediv_mux_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_clk_prediv_mux_init), CLKNODEMETHOD(clknode_set_mux, aw_clk_prediv_mux_set_mux), CLKNODEMETHOD(clknode_recalc_freq, aw_clk_prediv_mux_recalc), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_prediv_mux_clknode, aw_prediv_mux_clknode_class, aw_prediv_mux_clknode_methods, sizeof(struct aw_clk_prediv_mux_sc), clknode_class); int aw_clk_prediv_mux_register(struct clkdom *clkdom, struct aw_clk_prediv_mux_def *clkdef) { struct clknode *clk; struct aw_clk_prediv_mux_sc *sc; clk = clknode_create(clkdom, &aw_prediv_mux_clknode_class, &clkdef->clkdef); if (clk == NULL) return (1); sc = clknode_get_softc(clk); sc->offset = clkdef->offset; sc->mux_shift = clkdef->mux_shift; sc->mux_mask = ((1 << clkdef->mux_width) - 1) << sc->mux_shift; sc->div.shift = clkdef->div.shift; sc->div.mask = ((1 << clkdef->div.width) - 1) << sc->div.shift; sc->div.value = clkdef->div.value; sc->div.cond_shift = clkdef->div.cond_shift; sc->div.cond_mask = ((1 << clkdef->div.cond_width) - 1) << sc->div.shift; sc->div.cond_value = clkdef->div.cond_value; sc->div.flags = clkdef->div.flags; sc->prediv.shift = clkdef->prediv.shift; sc->prediv.mask = ((1 << clkdef->prediv.width) - 1) << sc->prediv.shift; sc->prediv.value = clkdef->prediv.value; sc->prediv.cond_shift = clkdef->prediv.cond_shift; if (clkdef->prediv.cond_width != 0) sc->prediv.cond_mask = ((1 << clkdef->prediv.cond_width) - 1) << sc->prediv.shift; else sc->prediv.cond_mask = clkdef->prediv.cond_mask; sc->prediv.cond_value = clkdef->prediv.cond_value; sc->prediv.flags = clkdef->prediv.flags; sc->flags = clkdef->flags; clknode_register(clkdom, clk); return (0); } Index: stable/12/sys/arm/allwinner/clkng/ccu_sun8i_r.c =================================================================== --- stable/12/sys/arm/allwinner/clkng/ccu_sun8i_r.c (revision 350627) +++ stable/12/sys/arm/allwinner/clkng/ccu_sun8i_r.c (revision 350628) @@ -1,264 +1,265 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2017,2018 Emmanuel Vadot * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #if defined(__aarch64__) #include "opt_soc.h" #endif #include #include #include #include #include #include /* Non-exported clocks */ #define CLK_AHB0 1 #define CLK_APB0 2 static struct aw_ccung_reset ccu_sun8i_r_resets[] = { CCU_RESET(RST_APB0_IR, 0xb0, 1) CCU_RESET(RST_APB0_TIMER, 0xb0, 2) - CCU_RESET(RST_APB0_RSB, 0xb0, 4) - CCU_RESET(RST_APB0_UART, 0xb0, 6) + CCU_RESET(RST_APB0_RSB, 0xb0, 3) + CCU_RESET(RST_APB0_UART, 0xb0, 4) + CCU_RESET(RST_APB0_I2C, 0xb0, 6) }; static struct aw_ccung_gate ccu_sun8i_r_gates[] = { CCU_GATE(CLK_APB0_PIO, "apb0-pio", "apb0", 0x28, 0) CCU_GATE(CLK_APB0_IR, "apb0-ir", "apb0", 0x28, 1) CCU_GATE(CLK_APB0_TIMER, "apb0-timer", "apb0", 0x28, 2) CCU_GATE(CLK_APB0_RSB, "apb0-rsb", "apb0", 0x28, 3) CCU_GATE(CLK_APB0_UART, "apb0-uart", "apb0", 0x28, 4) CCU_GATE(CLK_APB0_I2C, "apb0-i2c", "apb0", 0x28, 6) CCU_GATE(CLK_APB0_TWD, "apb0-twd", "apb0", 0x28, 7) }; static const char *ar100_parents[] = {"osc32k", "osc24M", "pll_periph0", "iosc"}; static const char *a83t_ar100_parents[] = {"osc16M-d512", "osc24M", "pll_periph", "osc16M"}; PREDIV_CLK(ar100_clk, CLK_AR100, /* id */ "ar100", ar100_parents, /* name, parents */ 0x00, /* offset */ 16, 2, /* mux */ 4, 2, 0, AW_CLK_FACTOR_POWER_OF_TWO, /* div */ 8, 5, 0, AW_CLK_FACTOR_HAS_COND, /* prediv */ 16, 2, 2); /* prediv condition */ PREDIV_CLK(a83t_ar100_clk, CLK_AR100, /* id */ "ar100", a83t_ar100_parents, /* name, parents */ 0x00, /* offset */ 16, 2, /* mux */ 4, 2, 0, AW_CLK_FACTOR_POWER_OF_TWO, /* div */ 8, 5, 0, AW_CLK_FACTOR_HAS_COND, /* prediv */ 16, 2, 2); /* prediv condition */ static const char *ahb0_parents[] = {"ar100"}; FIXED_CLK(ahb0_clk, CLK_AHB0, /* id */ "ahb0", /* name */ ahb0_parents, /* parent */ 0, /* freq */ 1, /* mult */ 1, /* div */ 0); /* flags */ static const char *apb0_parents[] = {"ahb0"}; DIV_CLK(apb0_clk, CLK_APB0, /* id */ "apb0", apb0_parents, /* name, parents */ 0x0c, /* offset */ 0, 2, /* shift, width */ 0, NULL); /* flags, div table */ static const char *r_ccu_ir_parents[] = {"osc32k", "osc24M"}; NM_CLK(r_ccu_ir_clk, CLK_IR, /* id */ "ir", r_ccu_ir_parents, /* names, parents */ 0x54, /* offset */ 0, 4, 0, 0, /* N factor */ 16, 2, 0, 0, /* M flags */ 24, 2, /* mux */ 31, /* gate */ AW_CLK_HAS_MUX | AW_CLK_REPARENT); /* flags */ static const char *a83t_ir_parents[] = {"osc16M", "osc24M"}; static struct aw_clk_nm_def a83t_ir_clk = { .clkdef = { .id = CLK_IR, .name = "ir", .parent_names = a83t_ir_parents, .parent_cnt = nitems(a83t_ir_parents), }, .offset = 0x54, .n = {.shift = 0, .width = 4, .flags = AW_CLK_FACTOR_POWER_OF_TWO, }, .m = {.shift = 16, .width = 2}, .prediv = { .cond_shift = 24, .cond_width = 2, .cond_value = 0, .value = 16 }, .mux_shift = 24, .mux_width = 2, .flags = AW_CLK_HAS_MUX | AW_CLK_HAS_PREDIV, }; static struct aw_ccung_clk clks[] = { { .type = AW_CLK_PREDIV_MUX, .clk.prediv_mux = &ar100_clk}, { .type = AW_CLK_DIV, .clk.div = &apb0_clk}, { .type = AW_CLK_FIXED, .clk.fixed = &ahb0_clk}, { .type = AW_CLK_NM, .clk.nm = &r_ccu_ir_clk}, }; static struct aw_ccung_clk a83t_clks[] = { { .type = AW_CLK_PREDIV_MUX, .clk.prediv_mux = &a83t_ar100_clk}, { .type = AW_CLK_DIV, .clk.div = &apb0_clk}, { .type = AW_CLK_FIXED, .clk.fixed = &ahb0_clk}, { .type = AW_CLK_NM, .clk.nm = &a83t_ir_clk}, }; static struct ofw_compat_data compat_data[] = { #if defined(SOC_ALLWINNER_H3) || defined(SOC_ALLWINNER_H5) { "allwinner,sun8i-h3-r-ccu", 1 }, #endif #if defined(SOC_ALLWINNER_A64) { "allwinner,sun50i-a64-r-ccu", 1 }, #endif { NULL, 0}, }; static int ccu_sun8i_r_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner SUN8I_R Clock Control Unit NG"); return (BUS_PROBE_DEFAULT); } static int ccu_sun8i_r_attach(device_t dev) { struct aw_ccung_softc *sc; sc = device_get_softc(dev); sc->resets = ccu_sun8i_r_resets; sc->nresets = nitems(ccu_sun8i_r_resets); sc->gates = ccu_sun8i_r_gates; sc->ngates = nitems(ccu_sun8i_r_gates); sc->clks = clks; sc->nclks = nitems(clks); return (aw_ccung_attach(dev)); } static device_method_t ccu_sun8i_r_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ccu_sun8i_r_probe), DEVMETHOD(device_attach, ccu_sun8i_r_attach), DEVMETHOD_END }; static devclass_t ccu_sun8i_r_devclass; DEFINE_CLASS_1(ccu_sun8i_r, ccu_sun8i_r_driver, ccu_sun8i_r_methods, sizeof(struct aw_ccung_softc), aw_ccung_driver); EARLY_DRIVER_MODULE(ccu_sun8i_r, simplebus, ccu_sun8i_r_driver, ccu_sun8i_r_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_LAST); static int ccu_a83t_r_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "allwinner,sun8i-a83t-r-ccu")) return (ENXIO); device_set_desc(dev, "Allwinner A83T_R Clock Control Unit NG"); return (BUS_PROBE_DEFAULT); } static int ccu_a83t_r_attach(device_t dev) { struct aw_ccung_softc *sc; sc = device_get_softc(dev); sc->resets = ccu_sun8i_r_resets; sc->nresets = nitems(ccu_sun8i_r_resets); sc->gates = ccu_sun8i_r_gates; sc->ngates = nitems(ccu_sun8i_r_gates); sc->clks = a83t_clks; sc->nclks = nitems(a83t_clks); return (aw_ccung_attach(dev)); } static device_method_t ccu_a83t_r_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ccu_a83t_r_probe), DEVMETHOD(device_attach, ccu_a83t_r_attach), DEVMETHOD_END }; static devclass_t ccu_a83t_r_devclass; DEFINE_CLASS_1(ccu_a83t_r, ccu_a83t_r_driver, ccu_a83t_r_methods, sizeof(struct aw_ccung_softc), aw_ccung_driver); EARLY_DRIVER_MODULE(ccu_a83t_r, simplebus, ccu_a83t_r_driver, ccu_a83t_r_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_LAST); Index: stable/12/sys/dev/iicbus/twsi/a10_twsi.c =================================================================== --- stable/12/sys/dev/iicbus/twsi/a10_twsi.c (revision 350627) +++ stable/12/sys/dev/iicbus/twsi/a10_twsi.c (revision 350628) @@ -1,182 +1,155 @@ /*- * Copyright (c) 2016-2019 Emmanuel Vadot * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #define TWI_ADDR 0x0 #define TWI_XADDR 0x4 #define TWI_DATA 0x8 #define TWI_CNTR 0xC #define TWI_STAT 0x10 #define TWI_CCR 0x14 #define TWI_SRST 0x18 #define TWI_EFR 0x1C #define TWI_LCR 0x20 static struct ofw_compat_data compat_data[] = { {"allwinner,sun4i-a10-i2c", 1}, {"allwinner,sun6i-a31-i2c", 1}, {"allwinner,sun8i-a83t-i2c", 1}, {NULL, 0}, }; static int a10_twsi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner Integrated I2C Bus Controller"); return (BUS_PROBE_DEFAULT); } static int a10_twsi_attach(device_t dev) { struct twsi_softc *sc; - clk_t clk; hwreset_t rst; - uint64_t freq; int error; sc = device_get_softc(dev); /* De-assert reset */ if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) == 0) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "could not de-assert reset\n"); return (error); } } /* Activate clock */ - error = clk_get_by_ofw_index(dev, 0, 0, &clk); + error = clk_get_by_ofw_index(dev, 0, 0, &sc->clk_core); if (error != 0) { device_printf(dev, "could not find clock\n"); return (error); } - error = clk_enable(clk); + error = clk_enable(sc->clk_core); if (error != 0) { device_printf(dev, "could not enable clock\n"); return (error); } sc->reg_data = TWI_DATA; sc->reg_slave_addr = TWI_ADDR; sc->reg_slave_ext_addr = TWI_XADDR; sc->reg_control = TWI_CNTR; sc->reg_status = TWI_STAT; sc->reg_baud_rate = TWI_CCR; sc->reg_soft_reset = TWI_SRST; - - /* Setup baud rate params */ - clk_get_freq(clk, &freq); - switch (freq) { - /* - * Formula is - * F0 = FINT / 2 ^ CLK_N - * F1 = F0 / (CLK_M + 1) - * - * Doc says that the output freq is F1/10 but my logic analyzer says otherwise - */ - case 48000000: - sc->baud_rate[IIC_SLOW].param = TWSI_BAUD_RATE_PARAM(11, 1); - sc->baud_rate[IIC_FAST].param = TWSI_BAUD_RATE_PARAM(11, 1); - sc->baud_rate[IIC_FASTEST].param = TWSI_BAUD_RATE_PARAM(2, 1); - break; - case 24000000: - sc->baud_rate[IIC_SLOW].param = TWSI_BAUD_RATE_PARAM(5, 2); - sc->baud_rate[IIC_FAST].param = TWSI_BAUD_RATE_PARAM(5, 2); - sc->baud_rate[IIC_FASTEST].param = TWSI_BAUD_RATE_PARAM(2, 2); - break; - default: - device_printf(dev, "Non supported frequency\n"); - return (ENXIO); - } sc->need_ack = true; return (twsi_attach(dev)); } static phandle_t a10_twsi_get_node(device_t bus, device_t dev) { return (ofw_bus_get_node(bus)); } static device_method_t a10_twsi_methods[] = { /* device interface */ DEVMETHOD(device_probe, a10_twsi_probe), DEVMETHOD(device_attach, a10_twsi_attach), /* OFW methods */ DEVMETHOD(ofw_bus_get_node, a10_twsi_get_node), { 0, 0 } }; DEFINE_CLASS_1(iichb, a10_twsi_driver, a10_twsi_methods, sizeof(struct twsi_softc), twsi_driver); static devclass_t a10_twsi_devclass; EARLY_DRIVER_MODULE(a10_twsi, simplebus, a10_twsi_driver, a10_twsi_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); EARLY_DRIVER_MODULE(iicbus, a10_twsi, iicbus_driver, iicbus_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); MODULE_DEPEND(a10_twsi, iicbus, 1, 1, 1); SIMPLEBUS_PNP_INFO(compat_data); Index: stable/12/sys/dev/iicbus/twsi/twsi.c =================================================================== --- stable/12/sys/dev/iicbus/twsi/twsi.c (revision 350627) +++ stable/12/sys/dev/iicbus/twsi/twsi.c (revision 350628) @@ -1,707 +1,757 @@ /*- * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. * All rights reserved. * * Developed by Semihalf. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of MARVELL nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Driver for the TWSI (aka I2C, aka IIC) bus controller found on Marvell * and Allwinner SoCs. Supports master operation only. * * Calls to DELAY() are needed per Application Note AN-179 "TWSI Software * Guidelines for Discovery(TM), Horizon (TM) and Feroceon(TM) Devices". */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #define TWSI_CONTROL_ACK (1 << 2) #define TWSI_CONTROL_IFLG (1 << 3) #define TWSI_CONTROL_STOP (1 << 4) #define TWSI_CONTROL_START (1 << 5) #define TWSI_CONTROL_TWSIEN (1 << 6) #define TWSI_CONTROL_INTEN (1 << 7) #define TWSI_STATUS_START 0x08 #define TWSI_STATUS_RPTD_START 0x10 #define TWSI_STATUS_ADDR_W_ACK 0x18 #define TWSI_STATUS_ADDR_W_NACK 0x20 #define TWSI_STATUS_DATA_WR_ACK 0x28 #define TWSI_STATUS_DATA_WR_NACK 0x30 #define TWSI_STATUS_ADDR_R_ACK 0x40 #define TWSI_STATUS_ADDR_R_NACK 0x48 #define TWSI_STATUS_DATA_RD_ACK 0x50 #define TWSI_STATUS_DATA_RD_NOACK 0x58 #define TWSI_DEBUG #undef TWSI_DEBUG #ifdef TWSI_DEBUG #define debugf(dev, fmt, args...) device_printf(dev, "%s: " fmt, __func__, ##args) #else #define debugf(dev, fmt, args...) #endif static struct resource_spec res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE}, { -1, 0 } }; static __inline uint32_t TWSI_READ(struct twsi_softc *sc, bus_size_t off) { uint32_t val; val = bus_read_4(sc->res[0], off); debugf(sc->dev, "read %x from %lx\n", val, off); return (val); } static __inline void TWSI_WRITE(struct twsi_softc *sc, bus_size_t off, uint32_t val) { debugf(sc->dev, "Writing %x to %lx\n", val, off); bus_write_4(sc->res[0], off, val); } static __inline void twsi_control_clear(struct twsi_softc *sc, uint32_t mask) { uint32_t val; val = TWSI_READ(sc, sc->reg_control); debugf(sc->dev, "read val=%x\n", val); val &= ~(TWSI_CONTROL_STOP | TWSI_CONTROL_START); val &= ~mask; debugf(sc->dev, "write val=%x\n", val); TWSI_WRITE(sc, sc->reg_control, val); } static __inline void twsi_control_set(struct twsi_softc *sc, uint32_t mask) { uint32_t val; val = TWSI_READ(sc, sc->reg_control); debugf(sc->dev, "read val=%x\n", val); val &= ~(TWSI_CONTROL_STOP | TWSI_CONTROL_START); val |= mask; debugf(sc->dev, "write val=%x\n", val); TWSI_WRITE(sc, sc->reg_control, val); } static __inline void twsi_clear_iflg(struct twsi_softc *sc) { DELAY(1000); twsi_control_clear(sc, TWSI_CONTROL_IFLG); DELAY(1000); } /* * timeout given in us * returns * 0 on successful mask change * non-zero on timeout */ static int twsi_poll_ctrl(struct twsi_softc *sc, int timeout, uint32_t mask) { timeout /= 10; debugf(sc->dev, "Waiting for ctrl reg to match mask %x\n", mask); while (!(TWSI_READ(sc, sc->reg_control) & mask)) { DELAY(10); if (--timeout < 0) return (timeout); } debugf(sc->dev, "done\n"); return (0); } /* * 'timeout' is given in us. Note also that timeout handling is not exact -- * twsi_locked_start() total wait can be more than 2 x timeout * (twsi_poll_ctrl() is called twice). 'mask' can be either TWSI_STATUS_START * or TWSI_STATUS_RPTD_START */ static int twsi_locked_start(device_t dev, struct twsi_softc *sc, int32_t mask, u_char slave, int timeout) { int read_access, iflg_set = 0; uint32_t status; mtx_assert(&sc->mutex, MA_OWNED); if (mask == TWSI_STATUS_RPTD_START) /* read IFLG to know if it should be cleared later; from NBSD */ iflg_set = TWSI_READ(sc, sc->reg_control) & TWSI_CONTROL_IFLG; debugf(dev, "send start\n"); twsi_control_set(sc, TWSI_CONTROL_START); if (mask == TWSI_STATUS_RPTD_START && iflg_set) { debugf(dev, "IFLG set, clearing (mask=%x)\n", mask); twsi_clear_iflg(sc); } /* * Without this delay we timeout checking IFLG if the timeout is 0. * NBSD driver always waits here too. */ DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { debugf(dev, "timeout sending %sSTART condition\n", mask == TWSI_STATUS_START ? "" : "repeated "); return (IIC_ETIMEOUT); } status = TWSI_READ(sc, sc->reg_status); debugf(dev, "status=%x\n", status); if (status != mask) { debugf(dev, "wrong status (%02x) after sending %sSTART condition\n", status, mask == TWSI_STATUS_START ? "" : "repeated "); return (IIC_ESTATUS); } TWSI_WRITE(sc, sc->reg_data, slave); twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { debugf(dev, "timeout sending slave address (timeout=%d)\n", timeout); return (IIC_ETIMEOUT); } read_access = (slave & 0x1) ? 1 : 0; status = TWSI_READ(sc, sc->reg_status); if (status != (read_access ? TWSI_STATUS_ADDR_R_ACK : TWSI_STATUS_ADDR_W_ACK)) { debugf(dev, "no ACK (status: %02x) after sending slave address\n", status); return (IIC_ENOACK); } return (IIC_NOERR); } +#ifdef EXT_RESOURCES +#define TWSI_BAUD_RATE_RAW(C,M,N) ((C)/((10*(M+1))<<(N))) +#define ABSSUB(a,b) (((a) > (b)) ? (a) - (b) : (b) - (a)) + +static int +twsi_calc_baud_rate(struct twsi_softc *sc, const u_int target, + int *param) +{ + uint64_t clk; + uint32_t cur, diff, diff0; + int m, n, m0, n0; + + /* Calculate baud rate. */ + diff0 = 0xffffffff; + + if (clk_get_freq(sc->clk_core, &clk) < 0) + return (-1); + + debugf(sc->dev, "Bus clock is at %lu\n", clk); + + for (n = 0; n < 8; n++) { + for (m = 0; m < 16; m++) { + cur = TWSI_BAUD_RATE_RAW(clk,m,n); + diff = ABSSUB(target, cur); + if (diff < diff0) { + m0 = m; + n0 = n; + diff0 = diff; + } + } + } + *param = TWSI_BAUD_RATE_PARAM(m0, n0); + + return (0); +} +#endif /* EXT_RESOURCES */ + /* * Only slave mode supported, disregard [old]addr */ static int twsi_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct twsi_softc *sc; uint32_t param; - /* uint32_t val; */ +#ifdef EXT_RESOURCES + u_int busfreq; +#endif sc = device_get_softc(dev); - switch (speed) { - case IIC_SLOW: - case IIC_FAST: - param = sc->baud_rate[speed].param; - debugf(dev, "Using IIC_FAST mode with speed param=%x\n", param); - break; - case IIC_FASTEST: - case IIC_UNKNOWN: - default: - param = sc->baud_rate[IIC_FAST].param; - debugf(dev, "Using IIC_FASTEST/UNKNOWN mode with speed param=%x\n", param); - break; +#ifdef EXT_RESOURCES + busfreq = IICBUS_GET_FREQUENCY(sc->iicbus, speed); + + if (twsi_calc_baud_rate(sc, busfreq, ¶m) == -1) { +#endif + switch (speed) { + case IIC_SLOW: + case IIC_FAST: + param = sc->baud_rate[speed].param; + debugf(dev, "Using IIC_FAST mode with speed param=%x\n", param); + break; + case IIC_FASTEST: + case IIC_UNKNOWN: + default: + param = sc->baud_rate[IIC_FAST].param; + debugf(dev, "Using IIC_FASTEST/UNKNOWN mode with speed param=%x\n", param); + break; + } +#ifdef EXT_RESOURCES } +#endif + debugf(dev, "Using clock param=%x\n", param); + mtx_lock(&sc->mutex); TWSI_WRITE(sc, sc->reg_soft_reset, 0x0); TWSI_WRITE(sc, sc->reg_baud_rate, param); TWSI_WRITE(sc, sc->reg_control, TWSI_CONTROL_TWSIEN); DELAY(1000); mtx_unlock(&sc->mutex); return (0); } static int twsi_stop(device_t dev) { struct twsi_softc *sc; sc = device_get_softc(dev); debugf(dev, "%s\n", __func__); mtx_lock(&sc->mutex); twsi_control_clear(sc, TWSI_CONTROL_ACK); twsi_control_set(sc, TWSI_CONTROL_STOP); twsi_clear_iflg(sc); DELAY(1000); mtx_unlock(&sc->mutex); return (IIC_NOERR); } /* * timeout is given in us */ static int twsi_repeated_start(device_t dev, u_char slave, int timeout) { struct twsi_softc *sc; int rv; sc = device_get_softc(dev); debugf(dev, "%s: slave=%x\n", __func__, slave); mtx_lock(&sc->mutex); rv = twsi_locked_start(dev, sc, TWSI_STATUS_RPTD_START, slave, timeout); mtx_unlock(&sc->mutex); if (rv) { twsi_stop(dev); return (rv); } else return (IIC_NOERR); } /* * timeout is given in us */ static int twsi_start(device_t dev, u_char slave, int timeout) { struct twsi_softc *sc; int rv; sc = device_get_softc(dev); debugf(dev, "%s: slave=%x\n", __func__, slave); mtx_lock(&sc->mutex); rv = twsi_locked_start(dev, sc, TWSI_STATUS_START, slave, timeout); mtx_unlock(&sc->mutex); if (rv) { twsi_stop(dev); return (rv); } else return (IIC_NOERR); } static int twsi_read(device_t dev, char *buf, int len, int *read, int last, int delay) { struct twsi_softc *sc; uint32_t status; int last_byte, rv; sc = device_get_softc(dev); mtx_lock(&sc->mutex); *read = 0; while (*read < len) { /* * Check if we are reading last byte of the last buffer, * do not send ACK then, per I2C specs */ last_byte = ((*read == len - 1) && last) ? 1 : 0; if (last_byte) twsi_control_clear(sc, TWSI_CONTROL_ACK); else twsi_control_set(sc, TWSI_CONTROL_ACK); twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, delay, TWSI_CONTROL_IFLG)) { debugf(dev, "timeout reading data (delay=%d)\n", delay); rv = IIC_ETIMEOUT; goto out; } status = TWSI_READ(sc, sc->reg_status); if (status != (last_byte ? TWSI_STATUS_DATA_RD_NOACK : TWSI_STATUS_DATA_RD_ACK)) { debugf(dev, "wrong status (%02x) while reading\n", status); rv = IIC_ESTATUS; goto out; } *buf++ = TWSI_READ(sc, sc->reg_data); (*read)++; } rv = IIC_NOERR; out: mtx_unlock(&sc->mutex); return (rv); } static int twsi_write(device_t dev, const char *buf, int len, int *sent, int timeout) { struct twsi_softc *sc; uint32_t status; int rv; sc = device_get_softc(dev); mtx_lock(&sc->mutex); *sent = 0; while (*sent < len) { TWSI_WRITE(sc, sc->reg_data, *buf++); twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { debugf(dev, "timeout writing data (timeout=%d)\n", timeout); rv = IIC_ETIMEOUT; goto out; } status = TWSI_READ(sc, sc->reg_status); if (status != TWSI_STATUS_DATA_WR_ACK) { debugf(dev, "wrong status (%02x) while writing\n", status); rv = IIC_ESTATUS; goto out; } (*sent)++; } rv = IIC_NOERR; out: mtx_unlock(&sc->mutex); return (rv); } static int twsi_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct twsi_softc *sc; int i; sc = device_get_softc(dev); if (sc->have_intr == false) return (iicbus_transfer_gen(dev, msgs, nmsgs)); sc->error = 0; sc->control_val = TWSI_CONTROL_TWSIEN | TWSI_CONTROL_INTEN | TWSI_CONTROL_ACK; TWSI_WRITE(sc, sc->reg_control, sc->control_val); debugf(dev, "transmitting %d messages\n", nmsgs); debugf(sc->dev, "status=%x\n", TWSI_READ(sc, sc->reg_status)); for (i = 0; i < nmsgs && sc->error == 0; i++) { sc->transfer = 1; sc->msg = &msgs[i]; debugf(dev, "msg[%d] flags: %x\n", i, msgs[i].flags); debugf(dev, "msg[%d] len: %d\n", i, msgs[i].len); /* Send start and re-enable interrupts */ sc->control_val = TWSI_CONTROL_TWSIEN | TWSI_CONTROL_INTEN | TWSI_CONTROL_ACK; if (sc->msg->len == 1) sc->control_val &= ~TWSI_CONTROL_ACK; TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_START); while (sc->error == 0 && sc->transfer != 0) { pause_sbt("twsi", SBT_1MS * 30, SBT_1MS, 0); } debugf(dev, "Done with msg[%d]\n", i); if (sc->error) { debugf(sc->dev, "Error, aborting (%d)\n", sc->error); TWSI_WRITE(sc, sc->reg_control, 0); goto out; } } /* Disable module and interrupts */ debugf(sc->dev, "status=%x\n", TWSI_READ(sc, sc->reg_status)); TWSI_WRITE(sc, sc->reg_control, 0); debugf(sc->dev, "status=%x\n", TWSI_READ(sc, sc->reg_status)); out: return (sc->error); } static void twsi_intr(void *arg) { struct twsi_softc *sc; uint32_t status; int transfer_done = 0; sc = arg; debugf(sc->dev, "Got interrupt\n"); while (TWSI_READ(sc, sc->reg_control) & TWSI_CONTROL_IFLG) { status = TWSI_READ(sc, sc->reg_status); debugf(sc->dev, "status=%x\n", status); switch (status) { case TWSI_STATUS_START: case TWSI_STATUS_RPTD_START: /* Transmit the address */ debugf(sc->dev, "Send the address\n"); if (sc->msg->flags & IIC_M_RD) TWSI_WRITE(sc, sc->reg_data, sc->msg->slave | LSB); else TWSI_WRITE(sc, sc->reg_data, sc->msg->slave & ~LSB); TWSI_WRITE(sc, sc->reg_control, sc->control_val); break; case TWSI_STATUS_ADDR_W_ACK: debugf(sc->dev, "Ack received after transmitting the address\n"); /* Directly send the first byte */ sc->sent_bytes = 0; debugf(sc->dev, "Sending byte 0 = %x\n", sc->msg->buf[0]); TWSI_WRITE(sc, sc->reg_data, sc->msg->buf[0]); TWSI_WRITE(sc, sc->reg_control, sc->control_val); break; case TWSI_STATUS_ADDR_R_ACK: + debugf(sc->dev, "Ack received after transmitting the address\n"); sc->recv_bytes = 0; TWSI_WRITE(sc, sc->reg_control, sc->control_val); break; case TWSI_STATUS_ADDR_W_NACK: case TWSI_STATUS_ADDR_R_NACK: debugf(sc->dev, "No ack received after transmitting the address\n"); sc->transfer = 0; sc->error = ETIMEDOUT; sc->control_val = 0; wakeup(sc); break; case TWSI_STATUS_DATA_WR_ACK: debugf(sc->dev, "Ack received after transmitting data\n"); if (sc->sent_bytes++ == (sc->msg->len - 1)) { debugf(sc->dev, "Done sending all the bytes\n"); /* Send stop, no interrupts on stop */ if (!(sc->msg->flags & IIC_M_NOSTOP)) { debugf(sc->dev, "Done TX data, send stop\n"); TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_STOP); } else { sc->control_val &= ~TWSI_CONTROL_INTEN; TWSI_WRITE(sc, sc->reg_control, sc->control_val); } transfer_done = 1; } else { debugf(sc->dev, "Sending byte %d = %x\n", sc->sent_bytes, sc->msg->buf[sc->sent_bytes]); TWSI_WRITE(sc, sc->reg_data, sc->msg->buf[sc->sent_bytes]); TWSI_WRITE(sc, sc->reg_control, sc->control_val); } break; case TWSI_STATUS_DATA_RD_ACK: debugf(sc->dev, "Ack received after receiving data\n"); debugf(sc->dev, "msg_len=%d recv_bytes=%d\n", sc->msg->len, sc->recv_bytes); sc->msg->buf[sc->recv_bytes++] = TWSI_READ(sc, sc->reg_data); /* If we only have one byte left, disable ACK */ if (sc->msg->len - sc->recv_bytes == 1) sc->control_val &= ~TWSI_CONTROL_ACK; TWSI_WRITE(sc, sc->reg_control, sc->control_val); break; case TWSI_STATUS_DATA_RD_NOACK: if (sc->msg->len - sc->recv_bytes == 1) { sc->msg->buf[sc->recv_bytes++] = TWSI_READ(sc, sc->reg_data); debugf(sc->dev, "Done RX data, send stop (2)\n"); if (!(sc->msg->flags & IIC_M_NOSTOP)) TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_STOP); } else { debugf(sc->dev, "No ack when receiving data\n"); sc->error = ENXIO; sc->control_val = 0; } sc->transfer = 0; transfer_done = 1; break; default: debugf(sc->dev, "status=%x hot handled\n", status); sc->transfer = 0; sc->error = ENXIO; sc->control_val = 0; wakeup(sc); break; } if (sc->need_ack) TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_IFLG); } debugf(sc->dev, "Done with interrupts\n"); if (transfer_done == 1) { sc->transfer = 0; wakeup(sc); } } static void twsi_intr_start(void *pdev) { struct twsi_softc *sc; sc = device_get_softc(pdev); if ((bus_setup_intr(pdev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, twsi_intr, sc, &sc->intrhand))) device_printf(pdev, "unable to register interrupt handler\n"); sc->have_intr = true; } int twsi_attach(device_t dev) { struct twsi_softc *sc; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->mutex, device_get_nameunit(dev), "twsi", MTX_DEF); if (bus_alloc_resources(dev, res_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); twsi_detach(dev); return (ENXIO); } /* Attach the iicbus. */ if ((sc->iicbus = device_add_child(dev, "iicbus", -1)) == NULL) { device_printf(dev, "could not allocate iicbus instance\n"); twsi_detach(dev); return (ENXIO); } bus_generic_attach(dev); config_intrhook_oneshot(twsi_intr_start, dev); return (0); } int twsi_detach(device_t dev) { struct twsi_softc *sc; int rv; sc = device_get_softc(dev); if ((rv = bus_generic_detach(dev)) != 0) return (rv); if (sc->iicbus != NULL) if ((rv = device_delete_child(dev, sc->iicbus)) != 0) return (rv); if (sc->intrhand != NULL) bus_teardown_intr(sc->dev, sc->res[1], sc->intrhand); bus_release_resources(dev, res_spec, sc->res); mtx_destroy(&sc->mutex); return (0); } static device_method_t twsi_methods[] = { /* device interface */ DEVMETHOD(device_detach, twsi_detach), /* Bus interface */ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), /* iicbus interface */ DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_repeated_start, twsi_repeated_start), DEVMETHOD(iicbus_start, twsi_start), DEVMETHOD(iicbus_stop, twsi_stop), DEVMETHOD(iicbus_write, twsi_write), DEVMETHOD(iicbus_read, twsi_read), DEVMETHOD(iicbus_reset, twsi_reset), DEVMETHOD(iicbus_transfer, twsi_transfer), { 0, 0 } }; DEFINE_CLASS_0(twsi, twsi_driver, twsi_methods, sizeof(struct twsi_softc)); Index: stable/12 =================================================================== --- stable/12 (revision 350627) +++ stable/12 (revision 350628) Property changes on: stable/12 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r347489-347491,347512