1*c2ac6d47Schristos /* $NetBSD: rdcpcib.c,v 1.3 2020/04/07 12:42:11 christos Exp $ */
268c8192bSbouyer
368c8192bSbouyer /*
468c8192bSbouyer * Copyright (c) 2011 Manuel Bouyer.
568c8192bSbouyer *
668c8192bSbouyer * Redistribution and use in source and binary forms, with or without
768c8192bSbouyer * modification, are permitted provided that the following conditions
868c8192bSbouyer * are met:
968c8192bSbouyer * 1. Redistributions of source code must retain the above copyright
1068c8192bSbouyer * notice, this list of conditions and the following disclaimer.
1168c8192bSbouyer * 2. Redistributions in binary form must reproduce the above copyright
1268c8192bSbouyer * notice, this list of conditions and the following disclaimer in the
1368c8192bSbouyer * documentation and/or other materials provided with the distribution.
1468c8192bSbouyer *
1568c8192bSbouyer * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1668c8192bSbouyer * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1768c8192bSbouyer * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1868c8192bSbouyer * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1968c8192bSbouyer * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2068c8192bSbouyer * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2168c8192bSbouyer * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2268c8192bSbouyer * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2368c8192bSbouyer * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2468c8192bSbouyer * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2568c8192bSbouyer */
2668c8192bSbouyer
2768c8192bSbouyer /*
2868c8192bSbouyer * driver for the RDC vortex86/PMX-1000 SoC PCI-ISA bridge, which also drives
2968c8192bSbouyer * the watchdog timer
3068c8192bSbouyer */
3168c8192bSbouyer
3268c8192bSbouyer
3368c8192bSbouyer #include <sys/cdefs.h>
34*c2ac6d47Schristos __KERNEL_RCSID(0, "$NetBSD: rdcpcib.c,v 1.3 2020/04/07 12:42:11 christos Exp $");
3568c8192bSbouyer
3668c8192bSbouyer #include <sys/types.h>
3768c8192bSbouyer #include <sys/param.h>
3868c8192bSbouyer #include <sys/systm.h>
3968c8192bSbouyer #include <sys/device.h>
4068c8192bSbouyer #include <sys/sysctl.h>
4168c8192bSbouyer #include <sys/timetc.h>
4268c8192bSbouyer #include <sys/gpio.h>
43391925c7Sdyoung #include <sys/bus.h>
4468c8192bSbouyer
4568c8192bSbouyer #include <dev/pci/pcivar.h>
4668c8192bSbouyer #include <dev/pci/pcireg.h>
4768c8192bSbouyer #include <dev/pci/pcidevs.h>
4868c8192bSbouyer
4968c8192bSbouyer #include <dev/gpio/gpiovar.h>
5068c8192bSbouyer #include <dev/sysmon/sysmonvar.h>
5168c8192bSbouyer
5268c8192bSbouyer #include "pcibvar.h"
5368c8192bSbouyer
5468c8192bSbouyer /*
5568c8192bSbouyer * special registers: iospace-registers for indirect access to timer and GPIO
5668c8192bSbouyer */
5768c8192bSbouyer #define RDC_IND_BASE 0x22
5868c8192bSbouyer #define RDC_IND_SIZE 0x2
5968c8192bSbouyer #define RDC_IND_ADDR 0
6068c8192bSbouyer #define RDC_IND_DATA 1
6168c8192bSbouyer
6268c8192bSbouyer struct rdcpcib_softc {
6368c8192bSbouyer struct pcib_softc rdc_pcib;
6468c8192bSbouyer
6568c8192bSbouyer /* indirect registers mapping */
6668c8192bSbouyer bus_space_tag_t rdc_iot;
6768c8192bSbouyer bus_space_handle_t rdc_ioh;
6868c8192bSbouyer
6968c8192bSbouyer /* Watchdog suppoprt */
7068c8192bSbouyer struct sysmon_wdog rdc_smw;
7168c8192bSbouyer };
7268c8192bSbouyer
7368c8192bSbouyer static int rdcpcibmatch(device_t, cfdata_t, void *);
7468c8192bSbouyer static void rdcpcibattach(device_t, device_t, void *);
7568c8192bSbouyer static int rdcpcibdetach(device_t, int);
7668c8192bSbouyer
7768c8192bSbouyer static uint8_t rdc_ind_read(struct rdcpcib_softc *, uint8_t);
7868c8192bSbouyer static void rdc_ind_write(struct rdcpcib_softc *, uint8_t, uint8_t);
7968c8192bSbouyer
8068c8192bSbouyer static void rdc_wdtimer_configure(device_t);
8168c8192bSbouyer static int rdc_wdtimer_unconfigure(device_t, int);
8268c8192bSbouyer static int rdc_wdtimer_setmode(struct sysmon_wdog *);
8368c8192bSbouyer static int rdc_wdtimer_tickle(struct sysmon_wdog *);
8468c8192bSbouyer static void rdc_wdtimer_stop(struct rdcpcib_softc *);
8568c8192bSbouyer static void rdc_wdtimer_start(struct rdcpcib_softc *);
8668c8192bSbouyer
8768c8192bSbouyer CFATTACH_DECL2_NEW(rdcpcib, sizeof(struct rdcpcib_softc),
8868c8192bSbouyer rdcpcibmatch, rdcpcibattach, rdcpcibdetach, NULL,
8968c8192bSbouyer pcibrescan, pcibchilddet);
9068c8192bSbouyer
91*c2ac6d47Schristos
92*c2ac6d47Schristos static const struct rdcpcib_device {
93*c2ac6d47Schristos pcireg_t vendor, product;
94*c2ac6d47Schristos } rdcpcib_devices[] = {
95*c2ac6d47Schristos { PCI_VENDOR_RDC, PCI_PRODUCT_RDC_R6011_PCIB},
96*c2ac6d47Schristos { PCI_VENDOR_RDC, PCI_PRODUCT_RDC_R6013_PCIB},
97*c2ac6d47Schristos { PCI_VENDOR_RDC, PCI_PRODUCT_RDC_R6031_PCIB},
98*c2ac6d47Schristos { PCI_VENDOR_RDC, PCI_PRODUCT_RDC_R6035_PCIB},
99*c2ac6d47Schristos { PCI_VENDOR_RDC, PCI_PRODUCT_RDC_R6036_PCIB},
100*c2ac6d47Schristos };
101*c2ac6d47Schristos
10268c8192bSbouyer static int
rdcpcibmatch(device_t parent,cfdata_t match,void * aux)10368c8192bSbouyer rdcpcibmatch(device_t parent, cfdata_t match, void *aux)
10468c8192bSbouyer {
10568c8192bSbouyer struct pci_attach_args *pa = aux;
10668c8192bSbouyer
10768c8192bSbouyer if (PCI_CLASS(pa->pa_class) != PCI_CLASS_BRIDGE ||
10868c8192bSbouyer PCI_SUBCLASS(pa->pa_class) != PCI_SUBCLASS_BRIDGE_ISA)
10968c8192bSbouyer return 0;
11068c8192bSbouyer
111*c2ac6d47Schristos for (size_t i = 0; i < __arraycount(rdcpcib_devices); i++) {
112*c2ac6d47Schristos if (PCI_VENDOR(pa->pa_id) == rdcpcib_devices[i].vendor &&
113*c2ac6d47Schristos PCI_PRODUCT(pa->pa_id) == rdcpcib_devices[i].product)
11468c8192bSbouyer return 10;
115*c2ac6d47Schristos }
11668c8192bSbouyer
11768c8192bSbouyer return 0;
11868c8192bSbouyer }
11968c8192bSbouyer
12068c8192bSbouyer static void
rdcpcibattach(device_t parent,device_t self,void * aux)12168c8192bSbouyer rdcpcibattach(device_t parent, device_t self, void *aux)
12268c8192bSbouyer {
12368c8192bSbouyer struct rdcpcib_softc *sc = device_private(self);
12468c8192bSbouyer
12568c8192bSbouyer /* generic PCI/ISA bridge */
12668c8192bSbouyer pcibattach(parent, self, aux);
12768c8192bSbouyer
12868c8192bSbouyer /* map indirect registers */
12968c8192bSbouyer sc->rdc_iot = x86_bus_space_io;
13068c8192bSbouyer if (bus_space_map(sc->rdc_iot, RDC_IND_BASE, RDC_IND_SIZE, 0,
13168c8192bSbouyer &sc->rdc_ioh) != 0) {
13268c8192bSbouyer aprint_error_dev(self, "couldn't map indirect registers\n");
13368c8192bSbouyer return;
13468c8192bSbouyer }
13568c8192bSbouyer
13668c8192bSbouyer /* Set up the watchdog. */
13768c8192bSbouyer rdc_wdtimer_configure(self);
13868c8192bSbouyer
13968c8192bSbouyer /* Install power handler XXX */
14068c8192bSbouyer if (!pmf_device_register(self, NULL, NULL))
14168c8192bSbouyer aprint_error_dev(self, "couldn't establish power handler\n");
14268c8192bSbouyer }
14368c8192bSbouyer
14468c8192bSbouyer static int
rdcpcibdetach(device_t self,int flags)14568c8192bSbouyer rdcpcibdetach(device_t self, int flags)
14668c8192bSbouyer {
14768c8192bSbouyer struct rdcpcib_softc *sc = device_private(self);
14868c8192bSbouyer int rc;
14968c8192bSbouyer
15068c8192bSbouyer pmf_device_deregister(self);
15168c8192bSbouyer
15268c8192bSbouyer if ((rc = rdc_wdtimer_unconfigure(self, flags)) != 0)
15368c8192bSbouyer return rc;
15468c8192bSbouyer
15568c8192bSbouyer bus_space_unmap(sc->rdc_iot, sc->rdc_ioh, RDC_IND_SIZE);
15668c8192bSbouyer return pcibdetach(self, flags);
15768c8192bSbouyer }
15868c8192bSbouyer
15968c8192bSbouyer /* indirect registers read/write */
16068c8192bSbouyer static uint8_t
rdc_ind_read(struct rdcpcib_softc * sc,uint8_t addr)16168c8192bSbouyer rdc_ind_read(struct rdcpcib_softc *sc, uint8_t addr)
16268c8192bSbouyer {
16368c8192bSbouyer bus_space_write_1(sc->rdc_iot, sc->rdc_ioh, RDC_IND_ADDR, addr);
16468c8192bSbouyer return bus_space_read_1(sc->rdc_iot, sc->rdc_ioh, RDC_IND_DATA);
16568c8192bSbouyer }
16668c8192bSbouyer
16768c8192bSbouyer static void
rdc_ind_write(struct rdcpcib_softc * sc,uint8_t addr,uint8_t data)16868c8192bSbouyer rdc_ind_write(struct rdcpcib_softc *sc, uint8_t addr, uint8_t data)
16968c8192bSbouyer {
17068c8192bSbouyer bus_space_write_1(sc->rdc_iot, sc->rdc_ioh, RDC_IND_ADDR, addr);
17168c8192bSbouyer bus_space_write_1(sc->rdc_iot, sc->rdc_ioh, RDC_IND_DATA, data);
17268c8192bSbouyer }
17368c8192bSbouyer
17468c8192bSbouyer /*
17568c8192bSbouyer * watchdog timer registers
17668c8192bSbouyer */
17768c8192bSbouyer
17868c8192bSbouyer /* control */
17968c8192bSbouyer #define RDC_WDT0_CTRL 0x37
18068c8192bSbouyer #define RDC_WDT0_CTRL_EN 0x40
18168c8192bSbouyer
18268c8192bSbouyer /* signal select */
18368c8192bSbouyer #define RDC_WDT0_SSEL 0x38
18468c8192bSbouyer #define RDC_WDT0_SSEL_MSK 0xf0
18568c8192bSbouyer #define RDC_WDT0_SSEL_NMI 0xc0
18668c8192bSbouyer #define RDC_WDT0_SSEL_RST 0xd0
18768c8192bSbouyer
18868c8192bSbouyer /* counter */
18968c8192bSbouyer #define RDC_WDT0_CNTL 0x39
19068c8192bSbouyer #define RDC_WDT0_CNTH 0x3A
19168c8192bSbouyer #define RDC_WDT0_CNTU 0x3B
19268c8192bSbouyer #define RDC_WDT0_FREQ 32768 /* Hz */
19368c8192bSbouyer #define RDC_WDT0_PERIOD_MAX (1 << 24)
19468c8192bSbouyer
19568c8192bSbouyer /* clear counter */
19668c8192bSbouyer #define RDC_WDT0_CTRL1 0x3c
19768c8192bSbouyer #define RDC_WDT0_CTRL1_RELOAD 0x40
19868c8192bSbouyer #define RDC_WDT0_CRTL1_FIRE 0x80
19968c8192bSbouyer
20068c8192bSbouyer
20168c8192bSbouyer /*
20268c8192bSbouyer * Initialize the watchdog timer.
20368c8192bSbouyer */
20468c8192bSbouyer static void
rdc_wdtimer_configure(device_t self)20568c8192bSbouyer rdc_wdtimer_configure(device_t self)
20668c8192bSbouyer {
20768c8192bSbouyer struct rdcpcib_softc *sc = device_private(self);
20868c8192bSbouyer uint8_t reg;
20968c8192bSbouyer
21068c8192bSbouyer /* Explicitly stop the timer. */
21168c8192bSbouyer rdc_wdtimer_stop(sc);
21268c8192bSbouyer
21368c8192bSbouyer /*
21468c8192bSbouyer * Register the driver with the sysmon watchdog framework.
21568c8192bSbouyer */
21668c8192bSbouyer sc->rdc_smw.smw_name = device_xname(self);
21768c8192bSbouyer sc->rdc_smw.smw_cookie = sc;
21868c8192bSbouyer sc->rdc_smw.smw_setmode = rdc_wdtimer_setmode;
21968c8192bSbouyer sc->rdc_smw.smw_tickle = rdc_wdtimer_tickle;
22068c8192bSbouyer sc->rdc_smw.smw_period = RDC_WDT0_PERIOD_MAX / RDC_WDT0_FREQ;
22168c8192bSbouyer
22268c8192bSbouyer if (sysmon_wdog_register(&sc->rdc_smw)) {
22368c8192bSbouyer aprint_error_dev(self, "unable to register wdt"
22468c8192bSbouyer "as a sysmon watchdog device.\n");
22568c8192bSbouyer return;
22668c8192bSbouyer }
22768c8192bSbouyer
22868c8192bSbouyer aprint_verbose_dev(self, "watchdog timer configured.\n");
22968c8192bSbouyer reg = rdc_ind_read(sc, RDC_WDT0_CTRL1);
23068c8192bSbouyer if (reg & RDC_WDT0_CRTL1_FIRE) {
23168c8192bSbouyer aprint_error_dev(self, "watchdog fired bit set, clearing\n");
23268c8192bSbouyer rdc_ind_write(sc, RDC_WDT0_CTRL1, reg & ~RDC_WDT0_CRTL1_FIRE);
23368c8192bSbouyer }
23468c8192bSbouyer }
23568c8192bSbouyer
23668c8192bSbouyer static int
rdc_wdtimer_unconfigure(device_t self,int flags)23768c8192bSbouyer rdc_wdtimer_unconfigure(device_t self, int flags)
23868c8192bSbouyer {
23968c8192bSbouyer struct rdcpcib_softc *sc = device_private(self);
24068c8192bSbouyer int rc;
24168c8192bSbouyer
24268c8192bSbouyer if ((rc = sysmon_wdog_unregister(&sc->rdc_smw)) != 0) {
24368c8192bSbouyer if (rc == ERESTART)
24468c8192bSbouyer rc = EINTR;
24568c8192bSbouyer return rc;
24668c8192bSbouyer }
24768c8192bSbouyer
24868c8192bSbouyer /* Explicitly stop the timer. */
24968c8192bSbouyer rdc_wdtimer_stop(sc);
25068c8192bSbouyer
25168c8192bSbouyer return 0;
25268c8192bSbouyer }
25368c8192bSbouyer
25468c8192bSbouyer
25568c8192bSbouyer /*
25668c8192bSbouyer * Sysmon watchdog callbacks.
25768c8192bSbouyer */
25868c8192bSbouyer static int
rdc_wdtimer_setmode(struct sysmon_wdog * smw)25968c8192bSbouyer rdc_wdtimer_setmode(struct sysmon_wdog *smw)
26068c8192bSbouyer {
26168c8192bSbouyer struct rdcpcib_softc *sc = smw->smw_cookie;
26268c8192bSbouyer unsigned int period;
26368c8192bSbouyer
26468c8192bSbouyer if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
26568c8192bSbouyer /* Stop the timer. */
26668c8192bSbouyer rdc_wdtimer_stop(sc);
26768c8192bSbouyer } else {
26868c8192bSbouyer period = smw->smw_period * RDC_WDT0_FREQ;
26968c8192bSbouyer if (period < 1 ||
27068c8192bSbouyer period > RDC_WDT0_PERIOD_MAX)
27168c8192bSbouyer return EINVAL;
27268c8192bSbouyer period = period - 1;
27368c8192bSbouyer
27468c8192bSbouyer /* Stop the timer, */
27568c8192bSbouyer rdc_wdtimer_stop(sc);
27668c8192bSbouyer
27768c8192bSbouyer /* set the timeout, */
27868c8192bSbouyer rdc_ind_write(sc, RDC_WDT0_CNTL, (period >> 0) & 0xff);
27968c8192bSbouyer rdc_ind_write(sc, RDC_WDT0_CNTH, (period >> 8) & 0xff);
28068c8192bSbouyer rdc_ind_write(sc, RDC_WDT0_CNTU, (period >> 16) & 0xff);
28168c8192bSbouyer
28268c8192bSbouyer /* and start the timer again */
28368c8192bSbouyer rdc_wdtimer_start(sc);
28468c8192bSbouyer }
28568c8192bSbouyer return 0;
28668c8192bSbouyer }
28768c8192bSbouyer
28868c8192bSbouyer static int
rdc_wdtimer_tickle(struct sysmon_wdog * smw)28968c8192bSbouyer rdc_wdtimer_tickle(struct sysmon_wdog *smw)
29068c8192bSbouyer {
29168c8192bSbouyer struct rdcpcib_softc *sc = smw->smw_cookie;
29268c8192bSbouyer uint8_t reg;
29368c8192bSbouyer
29468c8192bSbouyer reg = rdc_ind_read(sc, RDC_WDT0_CTRL1);
29568c8192bSbouyer rdc_ind_write(sc, RDC_WDT0_CTRL1, reg | RDC_WDT0_CTRL1_RELOAD);
29668c8192bSbouyer return 0;
29768c8192bSbouyer }
29868c8192bSbouyer
29968c8192bSbouyer static void
rdc_wdtimer_stop(struct rdcpcib_softc * sc)30068c8192bSbouyer rdc_wdtimer_stop(struct rdcpcib_softc *sc)
30168c8192bSbouyer {
30268c8192bSbouyer uint8_t reg;
30368c8192bSbouyer reg = rdc_ind_read(sc, RDC_WDT0_CTRL);
30468c8192bSbouyer rdc_ind_write(sc, RDC_WDT0_CTRL, reg & ~RDC_WDT0_CTRL_EN);
30568c8192bSbouyer }
30668c8192bSbouyer
30768c8192bSbouyer static void
rdc_wdtimer_start(struct rdcpcib_softc * sc)30868c8192bSbouyer rdc_wdtimer_start(struct rdcpcib_softc *sc)
30968c8192bSbouyer {
31068c8192bSbouyer uint8_t reg;
31168c8192bSbouyer rdc_ind_write(sc, RDC_WDT0_SSEL, RDC_WDT0_SSEL_RST);
31268c8192bSbouyer reg = rdc_ind_read(sc, RDC_WDT0_CTRL);
31368c8192bSbouyer rdc_ind_write(sc, RDC_WDT0_CTRL, reg | RDC_WDT0_CTRL_EN);
31468c8192bSbouyer }
315