mirror of
https://github.com/openbsd/src.git
synced 2026-04-15 09:44:36 +00:00
2248 lines
61 KiB
C
2248 lines
61 KiB
C
/* $OpenBSD: dwpcie.c,v 1.59 2026/04/07 20:38:51 kettenis Exp $ */
|
|
/*
|
|
* Copyright (c) 2018 Mark Kettenis <kettenis@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/device.h>
|
|
#include <sys/evcount.h>
|
|
#include <sys/extent.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <machine/intr.h>
|
|
#include <machine/bus.h>
|
|
#include <machine/fdt.h>
|
|
|
|
#include <dev/pci/pcidevs.h>
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcivar.h>
|
|
#include <dev/pci/ppbreg.h>
|
|
|
|
#include <dev/ofw/openfirm.h>
|
|
#include <dev/ofw/ofw_clock.h>
|
|
#include <dev/ofw/ofw_gpio.h>
|
|
#include <dev/ofw/ofw_misc.h>
|
|
#include <dev/ofw/ofw_pinctrl.h>
|
|
#include <dev/ofw/ofw_power.h>
|
|
#include <dev/ofw/ofw_regulator.h>
|
|
#include <dev/ofw/fdt.h>
|
|
|
|
/* Registers */
|
|
#define PCIE_PORT_LINK_CTRL 0x710
|
|
#define PCIE_PORT_LINK_CTRL_LANES_MASK (0x3f << 16)
|
|
#define PCIE_PORT_LINK_CTRL_LANES_1 (0x1 << 16)
|
|
#define PCIE_PORT_LINK_CTRL_LANES_2 (0x3 << 16)
|
|
#define PCIE_PORT_LINK_CTRL_LANES_4 (0x7 << 16)
|
|
#define PCIE_PORT_LINK_CTRL_LANES_8 (0xf << 16)
|
|
#define PCIE_PHY_DEBUG_R1 0x72c
|
|
#define PCIE_PHY_DEBUG_R1_XMLH_LINK_IN_TRAINING (1 << 29)
|
|
#define PCIE_PHY_DEBUG_R1_XMLH_LINK_UP (1 << 4)
|
|
#define PCIE_LINK_WIDTH_SPEED_CTRL 0x80c
|
|
#define PCIE_LINK_WIDTH_SPEED_CTRL_LANES_MASK (0x1f << 8)
|
|
#define PCIE_LINK_WIDTH_SPEED_CTRL_LANES_1 (0x1 << 8)
|
|
#define PCIE_LINK_WIDTH_SPEED_CTRL_LANES_2 (0x2 << 8)
|
|
#define PCIE_LINK_WIDTH_SPEED_CTRL_LANES_4 (0x4 << 8)
|
|
#define PCIE_LINK_WIDTH_SPEED_CTRL_LANES_8 (0x8 << 8)
|
|
#define PCIE_LINK_WIDTH_SPEED_CTRL_CHANGE (1 << 17)
|
|
|
|
#define PCIE_MSI_ADDR_LO 0x820
|
|
#define PCIE_MSI_ADDR_HI 0x824
|
|
#define PCIE_MSI_INTR_ENABLE(x) (0x828 + (x) * 12)
|
|
#define PCIE_MSI_INTR_MASK(x) (0x82c + (x) * 12)
|
|
#define PCIE_MSI_INTR_STATUS(x) (0x830 + (x) * 12)
|
|
|
|
#define MISC_CONTROL_1 0x8bc
|
|
#define MISC_CONTROL_1_DBI_RO_WR_EN (1 << 0)
|
|
#define IATU_VIEWPORT 0x900
|
|
#define IATU_VIEWPORT_INDEX0 0
|
|
#define IATU_VIEWPORT_INDEX1 1
|
|
#define IATU_VIEWPORT_INDEX2 2
|
|
#define IATU_VIEWPORT_INDEX3 3
|
|
#define IATU_OFFSET_VIEWPORT 0x904
|
|
#define IATU_OFFSET_UNROLL(x) (0x200 * (x))
|
|
#define IATU_REGION_CTRL_1 0x000
|
|
#define IATU_REGION_CTRL_1_TYPE_MEM 0
|
|
#define IATU_REGION_CTRL_1_TYPE_IO 2
|
|
#define IATU_REGION_CTRL_1_TYPE_CFG0 4
|
|
#define IATU_REGION_CTRL_1_TYPE_CFG1 5
|
|
#define IATU_REGION_CTRL_2 0x004
|
|
#define IATU_REGION_CTRL_2_REGION_EN (1U << 31)
|
|
#define IATU_LWR_BASE_ADDR 0x08
|
|
#define IATU_UPPER_BASE_ADDR 0x0c
|
|
#define IATU_LIMIT_ADDR 0x10
|
|
#define IATU_LWR_TARGET_ADDR 0x14
|
|
#define IATU_UPPER_TARGET_ADDR 0x18
|
|
|
|
/* Marvell ARMADA 8k registers */
|
|
#define PCIE_GLOBAL_CTRL 0x8000
|
|
#define PCIE_GLOBAL_CTRL_APP_LTSSM_EN (1 << 2)
|
|
#define PCIE_GLOBAL_CTRL_DEVICE_TYPE_MASK (0xf << 4)
|
|
#define PCIE_GLOBAL_CTRL_DEVICE_TYPE_RC (0x4 << 4)
|
|
#define PCIE_GLOBAL_STATUS 0x8008
|
|
#define PCIE_GLOBAL_STATUS_RDLH_LINK_UP (1 << 1)
|
|
#define PCIE_GLOBAL_STATUS_PHY_LINK_UP (1 << 9)
|
|
#define PCIE_PM_STATUS 0x8014
|
|
#define PCIE_GLOBAL_INT_CAUSE 0x801c
|
|
#define PCIE_GLOBAL_INT_MASK 0x8020
|
|
#define PCIE_GLOBAL_INT_MASK_INT_A (1 << 9)
|
|
#define PCIE_GLOBAL_INT_MASK_INT_B (1 << 10)
|
|
#define PCIE_GLOBAL_INT_MASK_INT_C (1 << 11)
|
|
#define PCIE_GLOBAL_INT_MASK_INT_D (1 << 12)
|
|
#define PCIE_ARCACHE_TRC 0x8050
|
|
#define PCIE_ARCACHE_TRC_DEFAULT 0x3511
|
|
#define PCIE_AWCACHE_TRC 0x8054
|
|
#define PCIE_AWCACHE_TRC_DEFAULT 0x5311
|
|
#define PCIE_ARUSER 0x805c
|
|
#define PCIE_AWUSER 0x8060
|
|
#define PCIE_AXUSER_DOMAIN_MASK (0x3 << 4)
|
|
#define PCIE_AXUSER_DOMAIN_INNER_SHARABLE (0x1 << 4)
|
|
#define PCIE_AXUSER_DOMAIN_OUTER_SHARABLE (0x2 << 4)
|
|
#define PCIE_STREAMID 0x8064
|
|
#define PCIE_STREAMID_FUNC_BITS(x) ((x) << 0)
|
|
#define PCIE_STREAMID_DEV_BITS(x) ((x) << 4)
|
|
#define PCIE_STREAMID_BUS_BITS(x) ((x) << 8)
|
|
#define PCIE_STREAMID_ROOTPORT(x) ((x) << 12)
|
|
#define PCIE_STREAMID_8040 \
|
|
(PCIE_STREAMID_ROOTPORT(0x80) | PCIE_STREAMID_BUS_BITS(2) | \
|
|
PCIE_STREAMID_DEV_BITS(2) | PCIE_STREAMID_FUNC_BITS(3))
|
|
|
|
/* Amlogic G12A registers */
|
|
#define PCIE_CFG0 0x0000
|
|
#define PCIE_CFG0_APP_LTSSM_EN (1 << 7)
|
|
#define PCIE_STATUS12 0x0030
|
|
#define PCIE_STATUS12_RDLH_LINK_UP (1 << 16)
|
|
#define PCIE_STATUS12_LTSSM_MASK (0x1f << 10)
|
|
#define PCIE_STATUS12_LTSSM_UP (0x11 << 10)
|
|
#define PCIE_STATUS12_SMLH_LINK_UP (1 << 6)
|
|
|
|
/* NXP i.MX8MQ registers */
|
|
#define PCIE_RC_LCR 0x7c
|
|
#define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN1 0x1
|
|
#define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN2 0x2
|
|
#define PCIE_RC_LCR_MAX_LINK_SPEEDS_MASK 0xf
|
|
#define PCIE_RC_LCR_L1EL_MASK (0x7 << 15)
|
|
#define PCIE_RC_LCR_L1EL_64US (0x6 << 15)
|
|
|
|
#define IOMUXC_GPR12 0x30
|
|
#define IMX8MQ_GPR_PCIE2_DEVICE_TYPE_MASK (0xf << 8)
|
|
#define IMX8MQ_GPR_PCIE2_DEVICE_TYPE_RC (0x4 << 8)
|
|
#define IMX8MQ_GPR_PCIE1_DEVICE_TYPE_MASK (0xf << 12)
|
|
#define IMX8MQ_GPR_PCIE1_DEVICE_TYPE_RC (0x4 << 12)
|
|
#define IOMUXC_GPR14 0x38
|
|
#define IOMUXC_GPR16 0x40
|
|
#define IMX8MQ_GPR_PCIE_REF_USE_PAD (1 << 9)
|
|
#define IMX8MQ_GPR_PCIE_CLK_REQ_OVERRIDE_EN (1 << 10)
|
|
#define IMX8MQ_GPR_PCIE_CLK_REQ_OVERRIDE (1 << 11)
|
|
#define IMX8MM_GPR_PCIE_SSC_EN (1 << 16)
|
|
#define IMX8MM_GPR_PCIE_POWER_OFF (1 << 17)
|
|
#define IMX8MM_GPR_PCIE_CMN_RST (1 << 18)
|
|
#define IMX8MM_GPR_PCIE_AUX_EN (1 << 19)
|
|
#define IMX8MM_GPR_PCIE_REF_CLK_MASK (0x3 << 24)
|
|
#define IMX8MM_GPR_PCIE_REF_CLK_PLL (0x3 << 24)
|
|
#define IMX8MM_GPR_PCIE_REF_CLK_EXT (0x2 << 24)
|
|
|
|
#define IMX8MM_PCIE_PHY_CMN_REG62 0x188
|
|
#define IMX8MM_PCIE_PHY_CMN_REG62_PLL_CLK_OUT 0x08
|
|
#define IMX8MM_PCIE_PHY_CMN_REG64 0x190
|
|
#define IMX8MM_PCIE_PHY_CMN_REG64_AUX_RX_TX_TERM 0x8c
|
|
#define IMX8MM_PCIE_PHY_CMN_REG75 0x1d4
|
|
#define IMX8MM_PCIE_PHY_CMN_REG75_PLL_DONE 0x3
|
|
#define IMX8MM_PCIE_PHY_TRSV_REG5 0x414
|
|
#define IMX8MM_PCIE_PHY_TRSV_REG5_GEN1_DEEMP 0x2d
|
|
#define IMX8MM_PCIE_PHY_TRSV_REG6 0x418
|
|
#define IMX8MM_PCIE_PHY_TRSV_REG6_GEN2_DEEMP 0xf
|
|
|
|
#define ANATOP_PLLOUT_CTL 0x74
|
|
#define ANATOP_PLLOUT_CTL_CKE (1 << 4)
|
|
#define ANATOP_PLLOUT_CTL_SEL_SYSPLL1 0xb
|
|
#define ANATOP_PLLOUT_CTL_SEL_MASK 0xf
|
|
#define ANATOP_PLLOUT_DIV 0x7c
|
|
#define ANATOP_PLLOUT_DIV_SYSPLL1 0x7
|
|
|
|
/* Rockchip RK3568/RK3588 registers */
|
|
#define PCIE_CLIENT_GENERAL_CON 0x0000
|
|
#define PCIE_CLIENT_DEV_TYPE_RC ((0xf << 4) << 16 | (0x4 << 4))
|
|
#define PCIE_CLIENT_LINK_REQ_RST_GRT ((1 << 3) << 16 | (1 << 3))
|
|
#define PCIE_CLIENT_APP_LTSSM_ENABLE ((1 << 2) << 16 | (1 << 2))
|
|
#define PCIE_CLIENT_INTR_STATUS_LEGACY 0x0008
|
|
#define PCIE_CLIENT_INTR_MASK_LEGACY 0x001c
|
|
#define PCIE_CLIENT_HOT_RESET_CTRL 0x0180
|
|
#define PCIE_CLIENT_APP_LTSSM_ENABLE_ENHANCE ((1 << 4) << 16 | (1 << 4))
|
|
#define PCIE_CLIENT_LTSSM_STATUS 0x0300
|
|
#define PCIE_CLIENT_RDLH_LINK_UP (1 << 17)
|
|
#define PCIE_CLIENT_SMLH_LINK_UP (1 << 16)
|
|
#define PCIE_CLIENT_LTSSM_MASK (0x1f << 0)
|
|
#define PCIE_CLIENT_LTSSM_UP (0x11 << 0)
|
|
|
|
#define HREAD4(sc, reg) \
|
|
(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
|
|
#define HWRITE4(sc, reg, val) \
|
|
bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
|
|
#define HSET4(sc, reg, bits) \
|
|
HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
|
|
#define HCLR4(sc, reg, bits) \
|
|
HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
|
|
|
|
struct dwpcie_range {
|
|
uint32_t flags;
|
|
uint64_t pci_base;
|
|
uint64_t phys_base;
|
|
uint64_t size;
|
|
};
|
|
|
|
struct dwpcie_intx {
|
|
int (*di_func)(void *);
|
|
void *di_arg;
|
|
int di_ipl;
|
|
int di_flags;
|
|
int di_pin;
|
|
struct evcount di_count;
|
|
char *di_name;
|
|
struct dwpcie_softc *di_sc;
|
|
TAILQ_ENTRY(dwpcie_intx) di_next;
|
|
};
|
|
|
|
#define DWPCIE_MAX_MSI 64
|
|
|
|
struct dwpcie_msi {
|
|
int (*dm_func)(void *);
|
|
void *dm_arg;
|
|
int dm_ipl;
|
|
int dm_flags;
|
|
int dm_vec;
|
|
int dm_nvec;
|
|
struct evcount dm_count;
|
|
char *dm_name;
|
|
};
|
|
|
|
struct dwpcie_softc {
|
|
struct device sc_dev;
|
|
bus_space_tag_t sc_iot;
|
|
bus_space_handle_t sc_ioh;
|
|
bus_dma_tag_t sc_dmat;
|
|
|
|
bus_addr_t sc_ctrl_base;
|
|
bus_size_t sc_ctrl_size;
|
|
|
|
bus_addr_t sc_conf_base;
|
|
bus_size_t sc_conf_size;
|
|
bus_space_handle_t sc_conf_ioh;
|
|
|
|
bus_addr_t sc_glue_base;
|
|
bus_size_t sc_glue_size;
|
|
bus_space_handle_t sc_glue_ioh;
|
|
|
|
bus_addr_t sc_atu_base;
|
|
bus_size_t sc_atu_size;
|
|
bus_space_handle_t sc_atu_ioh;
|
|
|
|
bus_addr_t sc_io_base;
|
|
bus_addr_t sc_io_bus_addr;
|
|
bus_size_t sc_io_size;
|
|
bus_addr_t sc_mem_base;
|
|
bus_addr_t sc_mem_bus_addr;
|
|
bus_size_t sc_mem_size;
|
|
bus_addr_t sc_pmem_base;
|
|
bus_addr_t sc_pmem_bus_addr;
|
|
bus_size_t sc_pmem_size;
|
|
|
|
int sc_node;
|
|
int sc_acells;
|
|
int sc_scells;
|
|
int sc_pacells;
|
|
int sc_pscells;
|
|
struct dwpcie_range *sc_ranges;
|
|
int sc_nranges;
|
|
|
|
struct bus_space sc_bus_iot;
|
|
struct bus_space sc_bus_memt;
|
|
|
|
struct machine_pci_chipset sc_pc;
|
|
int sc_bus;
|
|
|
|
int sc_num_viewport;
|
|
int sc_atu_unroll;
|
|
int sc_atu_viewport;
|
|
|
|
void *sc_ih;
|
|
struct interrupt_controller sc_ic;
|
|
TAILQ_HEAD(,dwpcie_intx) sc_intx[4];
|
|
|
|
void *sc_msi_ih[2];
|
|
uint64_t sc_msi_addr;
|
|
uint64_t sc_msi_mask;
|
|
struct dwpcie_msi sc_msi[DWPCIE_MAX_MSI];
|
|
int sc_num_msi;
|
|
};
|
|
|
|
struct dwpcie_intr_handle {
|
|
struct machine_intr_handle pih_ih;
|
|
struct dwpcie_softc *pih_sc;
|
|
struct dwpcie_msi *pih_dm;
|
|
bus_dma_tag_t pih_dmat;
|
|
bus_dmamap_t pih_map;
|
|
};
|
|
|
|
int dwpcie_match(struct device *, void *, void *);
|
|
void dwpcie_attach(struct device *, struct device *, void *);
|
|
|
|
const struct cfattach dwpcie_ca = {
|
|
sizeof (struct dwpcie_softc), dwpcie_match, dwpcie_attach
|
|
};
|
|
|
|
struct cfdriver dwpcie_cd = {
|
|
NULL, "dwpcie", DV_DULL
|
|
};
|
|
|
|
int
|
|
dwpcie_match(struct device *parent, void *match, void *aux)
|
|
{
|
|
struct fdt_attach_args *faa = aux;
|
|
|
|
return (OF_is_compatible(faa->fa_node, "amlogic,g12a-pcie") ||
|
|
OF_is_compatible(faa->fa_node, "baikal,bm1000-pcie") ||
|
|
OF_is_compatible(faa->fa_node, "fsl,imx8mm-pcie") ||
|
|
OF_is_compatible(faa->fa_node, "fsl,imx8mq-pcie") ||
|
|
OF_is_compatible(faa->fa_node, "marvell,armada8k-pcie") ||
|
|
OF_is_compatible(faa->fa_node, "qcom,pcie-sc7280") ||
|
|
OF_is_compatible(faa->fa_node, "qcom,pcie-sc8280xp") ||
|
|
OF_is_compatible(faa->fa_node, "qcom,pcie-x1e80100") ||
|
|
OF_is_compatible(faa->fa_node, "rockchip,rk3568-pcie") ||
|
|
OF_is_compatible(faa->fa_node, "rockchip,rk3588-pcie") ||
|
|
OF_is_compatible(faa->fa_node, "sifive,fu740-pcie") ||
|
|
OF_is_compatible(faa->fa_node, "spacemit,k1-pcie"));
|
|
}
|
|
|
|
void dwpcie_attach_deferred(struct device *);
|
|
|
|
void dwpcie_atu_disable(struct dwpcie_softc *, int);
|
|
void dwpcie_atu_config(struct dwpcie_softc *, int, int,
|
|
uint64_t, uint64_t, uint64_t);
|
|
void dwpcie_link_config(struct dwpcie_softc *);
|
|
int dwpcie_link_up(struct dwpcie_softc *);
|
|
|
|
int dwpcie_armada8k_init(struct dwpcie_softc *);
|
|
int dwpcie_armada8k_link_up(struct dwpcie_softc *);
|
|
int dwpcie_armada8k_intr(void *);
|
|
|
|
int dwpcie_g12a_init(struct dwpcie_softc *);
|
|
int dwpcie_g12a_link_up(struct dwpcie_softc *);
|
|
|
|
int dwpcie_imx8mq_init(struct dwpcie_softc *);
|
|
int dwpcie_imx8mq_intr(void *);
|
|
|
|
int dwpcie_fu740_init(struct dwpcie_softc *);
|
|
|
|
int dwpcie_k1_init(struct dwpcie_softc *);
|
|
|
|
int dwpcie_rk3568_init(struct dwpcie_softc *);
|
|
int dwpcie_rk3568_intr(void *);
|
|
void *dwpcie_rk3568_intr_establish(void *, int *, int,
|
|
struct cpu_info *, int (*)(void *), void *, char *);
|
|
void dwpcie_rk3568_intr_disestablish(void *);
|
|
void dwpcie_rk3568_intr_barrier(void *);
|
|
|
|
int dwpcie_sc7280_init(struct dwpcie_softc *);
|
|
|
|
void dwpcie_attach_hook(struct device *, struct device *,
|
|
struct pcibus_attach_args *);
|
|
int dwpcie_bus_maxdevs(void *, int);
|
|
pcitag_t dwpcie_make_tag(void *, int, int, int);
|
|
void dwpcie_decompose_tag(void *, pcitag_t, int *, int *, int *);
|
|
int dwpcie_conf_size(void *, pcitag_t);
|
|
pcireg_t dwpcie_conf_read(void *, pcitag_t, int);
|
|
void dwpcie_conf_write(void *, pcitag_t, int, pcireg_t);
|
|
int dwpcie_probe_device_hook(void *, struct pci_attach_args *);
|
|
|
|
int dwpcie_intr_map(struct pci_attach_args *, pci_intr_handle_t *);
|
|
const char *dwpcie_intr_string(void *, pci_intr_handle_t);
|
|
void *dwpcie_intr_establish(void *, pci_intr_handle_t, int,
|
|
struct cpu_info *, int (*)(void *), void *, char *);
|
|
void dwpcie_intr_disestablish(void *, void *);
|
|
|
|
int dwpcie_bs_iomap(bus_space_tag_t, bus_addr_t, bus_size_t, int,
|
|
bus_space_handle_t *);
|
|
int dwpcie_bs_memmap(bus_space_tag_t, bus_addr_t, bus_size_t, int,
|
|
bus_space_handle_t *);
|
|
|
|
struct interrupt_controller dwpcie_ic = {
|
|
.ic_barrier = intr_barrier
|
|
};
|
|
|
|
void
|
|
dwpcie_attach(struct device *parent, struct device *self, void *aux)
|
|
{
|
|
struct dwpcie_softc *sc = (struct dwpcie_softc *)self;
|
|
struct fdt_attach_args *faa = aux;
|
|
uint32_t *ranges;
|
|
int i, j, nranges, rangeslen;
|
|
int atu, config, ctrl, glue;
|
|
|
|
if (faa->fa_nreg < 2) {
|
|
printf(": no registers\n");
|
|
return;
|
|
}
|
|
|
|
sc->sc_ctrl_base = faa->fa_reg[0].addr;
|
|
sc->sc_ctrl_size = faa->fa_reg[0].size;
|
|
|
|
ctrl = OF_getindex(faa->fa_node, "dbi", "reg-names");
|
|
if (ctrl >= 0 && ctrl < faa->fa_nreg) {
|
|
sc->sc_ctrl_base = faa->fa_reg[ctrl].addr;
|
|
sc->sc_ctrl_size = faa->fa_reg[ctrl].size;
|
|
}
|
|
|
|
config = OF_getindex(faa->fa_node, "config", "reg-names");
|
|
if (config < 0 || config >= faa->fa_nreg) {
|
|
printf(": no config registers\n");
|
|
return;
|
|
}
|
|
|
|
sc->sc_conf_base = faa->fa_reg[config].addr;
|
|
sc->sc_conf_size = faa->fa_reg[config].size;
|
|
|
|
sc->sc_atu_base = sc->sc_ctrl_base + 0x300000;
|
|
sc->sc_atu_size = sc->sc_ctrl_size - 0x300000;
|
|
|
|
atu = OF_getindex(faa->fa_node, "atu", "reg-names");
|
|
if (atu >= 0 && atu < faa->fa_nreg) {
|
|
sc->sc_atu_base = faa->fa_reg[atu].addr;
|
|
sc->sc_atu_size = faa->fa_reg[atu].size;
|
|
}
|
|
|
|
if (OF_is_compatible(faa->fa_node, "amlogic,g12a-pcie")) {
|
|
glue = OF_getindex(faa->fa_node, "cfg", "reg-names");
|
|
if (glue < 0 || glue >= faa->fa_nreg) {
|
|
printf(": no glue registers\n");
|
|
return;
|
|
}
|
|
|
|
sc->sc_glue_base = faa->fa_reg[glue].addr;
|
|
sc->sc_glue_size = faa->fa_reg[glue].size;
|
|
}
|
|
|
|
if (OF_is_compatible(faa->fa_node, "rockchip,rk3568-pcie") ||
|
|
OF_is_compatible(faa->fa_node, "rockchip,rk3588-pcie")) {
|
|
glue = OF_getindex(faa->fa_node, "apb", "reg-names");
|
|
if (glue < 0 || glue >= faa->fa_nreg) {
|
|
printf(": no glue registers\n");
|
|
return;
|
|
}
|
|
|
|
sc->sc_glue_base = faa->fa_reg[glue].addr;
|
|
sc->sc_glue_size = faa->fa_reg[glue].size;
|
|
}
|
|
|
|
if (OF_is_compatible(faa->fa_node, "spacemit,k1-pcie")) {
|
|
glue = OF_getindex(faa->fa_node, "link", "reg-names");
|
|
if (glue < 0 || glue >= faa->fa_nreg) {
|
|
printf(": no link registers\n");
|
|
return;
|
|
}
|
|
|
|
sc->sc_glue_base = faa->fa_reg[glue].addr;
|
|
sc->sc_glue_size = faa->fa_reg[glue].size;
|
|
}
|
|
|
|
sc->sc_iot = faa->fa_iot;
|
|
sc->sc_dmat = faa->fa_dmat;
|
|
sc->sc_node = faa->fa_node;
|
|
|
|
sc->sc_acells = OF_getpropint(sc->sc_node, "#address-cells",
|
|
faa->fa_acells);
|
|
sc->sc_scells = OF_getpropint(sc->sc_node, "#size-cells",
|
|
faa->fa_scells);
|
|
sc->sc_pacells = faa->fa_acells;
|
|
sc->sc_pscells = faa->fa_scells;
|
|
|
|
rangeslen = OF_getproplen(sc->sc_node, "ranges");
|
|
if (rangeslen <= 0 || (rangeslen % sizeof(uint32_t)) ||
|
|
(rangeslen / sizeof(uint32_t)) % (sc->sc_acells +
|
|
sc->sc_pacells + sc->sc_scells)) {
|
|
printf(": invalid ranges property\n");
|
|
return;
|
|
}
|
|
|
|
ranges = malloc(rangeslen, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(sc->sc_node, "ranges", ranges,
|
|
rangeslen);
|
|
|
|
nranges = (rangeslen / sizeof(uint32_t)) /
|
|
(sc->sc_acells + sc->sc_pacells + sc->sc_scells);
|
|
sc->sc_ranges = mallocarray(nranges,
|
|
sizeof(struct dwpcie_range), M_TEMP, M_WAITOK);
|
|
sc->sc_nranges = nranges;
|
|
|
|
for (i = 0, j = 0; i < sc->sc_nranges; i++) {
|
|
sc->sc_ranges[i].flags = ranges[j++];
|
|
sc->sc_ranges[i].pci_base = ranges[j++];
|
|
if (sc->sc_acells - 1 == 2) {
|
|
sc->sc_ranges[i].pci_base <<= 32;
|
|
sc->sc_ranges[i].pci_base |= ranges[j++];
|
|
}
|
|
sc->sc_ranges[i].phys_base = ranges[j++];
|
|
if (sc->sc_pacells == 2) {
|
|
sc->sc_ranges[i].phys_base <<= 32;
|
|
sc->sc_ranges[i].phys_base |= ranges[j++];
|
|
}
|
|
sc->sc_ranges[i].size = ranges[j++];
|
|
if (sc->sc_scells == 2) {
|
|
sc->sc_ranges[i].size <<= 32;
|
|
sc->sc_ranges[i].size |= ranges[j++];
|
|
}
|
|
}
|
|
|
|
free(ranges, M_TEMP, rangeslen);
|
|
|
|
if (bus_space_map(sc->sc_iot, sc->sc_ctrl_base,
|
|
sc->sc_ctrl_size, 0, &sc->sc_ioh)) {
|
|
free(sc->sc_ranges, M_TEMP, sc->sc_nranges *
|
|
sizeof(struct dwpcie_range));
|
|
printf(": can't map ctrl registers\n");
|
|
return;
|
|
}
|
|
|
|
if (bus_space_map(sc->sc_iot, sc->sc_conf_base,
|
|
sc->sc_conf_size, 0, &sc->sc_conf_ioh)) {
|
|
bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ctrl_size);
|
|
free(sc->sc_ranges, M_TEMP, sc->sc_nranges *
|
|
sizeof(struct dwpcie_range));
|
|
printf(": can't map config registers\n");
|
|
return;
|
|
}
|
|
|
|
sc->sc_num_viewport = OF_getpropint(sc->sc_node, "num-viewport", 2);
|
|
|
|
printf("\n");
|
|
|
|
pinctrl_byname(sc->sc_node, "default");
|
|
clock_set_assigned(sc->sc_node);
|
|
|
|
config_defer(self, dwpcie_attach_deferred);
|
|
}
|
|
|
|
void
|
|
dwpcie_attach_deferred(struct device *self)
|
|
{
|
|
struct dwpcie_softc *sc = (struct dwpcie_softc *)self;
|
|
struct pcibus_attach_args pba;
|
|
bus_addr_t iobase, iolimit;
|
|
bus_addr_t membase, memlimit;
|
|
bus_addr_t pmembase, pmemlimit;
|
|
uint32_t bus_range[2];
|
|
pcireg_t bir, blr, csr;
|
|
int i, error = 0;
|
|
|
|
if (OF_is_compatible(sc->sc_node, "marvell,armada8k-pcie"))
|
|
error = dwpcie_armada8k_init(sc);
|
|
if (OF_is_compatible(sc->sc_node, "amlogic,g12a-pcie"))
|
|
error = dwpcie_g12a_init(sc);
|
|
if (OF_is_compatible(sc->sc_node, "fsl,imx8mm-pcie") ||
|
|
OF_is_compatible(sc->sc_node, "fsl,imx8mq-pcie"))
|
|
error = dwpcie_imx8mq_init(sc);
|
|
if (OF_is_compatible(sc->sc_node, "qcom,pcie-sc7280") ||
|
|
OF_is_compatible(sc->sc_node, "qcom,pcie-sc8280xp") ||
|
|
OF_is_compatible(sc->sc_node, "qcom,pcie-x1e80100"))
|
|
error = dwpcie_sc7280_init(sc);
|
|
if (OF_is_compatible(sc->sc_node, "rockchip,rk3568-pcie") ||
|
|
OF_is_compatible(sc->sc_node, "rockchip,rk3588-pcie"))
|
|
error = dwpcie_rk3568_init(sc);
|
|
if (OF_is_compatible(sc->sc_node, "sifive,fu740-pcie"))
|
|
error = dwpcie_fu740_init(sc);
|
|
if (OF_is_compatible(sc->sc_node, "spacemit,k1-pcie"))
|
|
error = dwpcie_k1_init(sc);
|
|
if (error != 0) {
|
|
bus_space_unmap(sc->sc_iot, sc->sc_conf_ioh, sc->sc_conf_size);
|
|
bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ctrl_size);
|
|
free(sc->sc_ranges, M_TEMP, sc->sc_nranges *
|
|
sizeof(struct dwpcie_range));
|
|
printf("%s: can't initialize hardware\n",
|
|
sc->sc_dev.dv_xname);
|
|
return;
|
|
}
|
|
|
|
sc->sc_atu_viewport = -1;
|
|
if (HREAD4(sc, IATU_VIEWPORT) == 0xffffffff) {
|
|
sc->sc_atu_unroll = 1;
|
|
if (bus_space_map(sc->sc_iot, sc->sc_atu_base,
|
|
sc->sc_atu_size, 0, &sc->sc_atu_ioh)) {
|
|
bus_space_unmap(sc->sc_iot, sc->sc_conf_ioh,
|
|
sc->sc_conf_size);
|
|
bus_space_unmap(sc->sc_iot, sc->sc_ioh,
|
|
sc->sc_ctrl_size);
|
|
free(sc->sc_ranges, M_TEMP, sc->sc_nranges *
|
|
sizeof(struct dwpcie_range));
|
|
printf("%s: can't map atu registers\n",
|
|
sc->sc_dev.dv_xname);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Set up address translation for I/O space. */
|
|
for (i = 0; i < sc->sc_nranges; i++) {
|
|
if ((sc->sc_ranges[i].flags & 0x03000000) == 0x01000000 &&
|
|
sc->sc_ranges[i].size > 0) {
|
|
sc->sc_io_base = sc->sc_ranges[i].phys_base;
|
|
sc->sc_io_bus_addr = sc->sc_ranges[i].pci_base;
|
|
sc->sc_io_size = sc->sc_ranges[i].size;
|
|
}
|
|
if ((sc->sc_ranges[i].flags & 0x03000000) == 0x02000000 &&
|
|
sc->sc_ranges[i].size > 0) {
|
|
sc->sc_mem_base = sc->sc_ranges[i].phys_base;
|
|
sc->sc_mem_bus_addr = sc->sc_ranges[i].pci_base;
|
|
sc->sc_mem_size = sc->sc_ranges[i].size;
|
|
}
|
|
if ((sc->sc_ranges[i].flags & 0x03000000) == 0x03000000 &&
|
|
sc->sc_ranges[i].size > 0) {
|
|
sc->sc_pmem_base = sc->sc_ranges[i].phys_base;
|
|
sc->sc_pmem_bus_addr = sc->sc_ranges[i].pci_base;
|
|
sc->sc_pmem_size = sc->sc_ranges[i].size;
|
|
}
|
|
}
|
|
if (sc->sc_mem_size == 0) {
|
|
printf("%s: no memory mapped I/O window\n",
|
|
sc->sc_dev.dv_xname);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Disable prefetchable memory mapped I/O window if we don't
|
|
* have enough viewports to enable it.
|
|
*/
|
|
if (sc->sc_num_viewport < 4)
|
|
sc->sc_pmem_size = 0;
|
|
|
|
for (i = 0; i < sc->sc_num_viewport; i++)
|
|
dwpcie_atu_disable(sc, i);
|
|
|
|
dwpcie_atu_config(sc, IATU_VIEWPORT_INDEX0,
|
|
IATU_REGION_CTRL_1_TYPE_MEM, sc->sc_mem_base,
|
|
sc->sc_mem_bus_addr, sc->sc_mem_size);
|
|
if (sc->sc_num_viewport > 2 && sc->sc_io_size > 0)
|
|
dwpcie_atu_config(sc, IATU_VIEWPORT_INDEX2,
|
|
IATU_REGION_CTRL_1_TYPE_IO, sc->sc_io_base,
|
|
sc->sc_io_bus_addr, sc->sc_io_size);
|
|
if (sc->sc_num_viewport > 3 && sc->sc_pmem_size > 0)
|
|
dwpcie_atu_config(sc, IATU_VIEWPORT_INDEX3,
|
|
IATU_REGION_CTRL_1_TYPE_MEM, sc->sc_pmem_base,
|
|
sc->sc_pmem_bus_addr, sc->sc_pmem_size);
|
|
|
|
/* Enable modification of read-only bits. */
|
|
HSET4(sc, MISC_CONTROL_1, MISC_CONTROL_1_DBI_RO_WR_EN);
|
|
|
|
/* A Root Port is a PCI-PCI Bridge. */
|
|
HWRITE4(sc, PCI_CLASS_REG,
|
|
PCI_CLASS_BRIDGE << PCI_CLASS_SHIFT |
|
|
PCI_SUBCLASS_BRIDGE_PCI << PCI_SUBCLASS_SHIFT);
|
|
|
|
/* Clear BAR as U-Boot seems to leave garbage in it. */
|
|
HWRITE4(sc, PCI_MAPREG_START, PCI_MAPREG_MEM_TYPE_64BIT);
|
|
HWRITE4(sc, PCI_MAPREG_START + 4, 0);
|
|
|
|
/* Enable 32-bit I/O addressing. */
|
|
HSET4(sc, PPB_REG_IOSTATUS,
|
|
PPB_IO_32BIT | (PPB_IO_32BIT << PPB_IOLIMIT_SHIFT));
|
|
|
|
/* Make sure read-only bits are write-protected. */
|
|
HCLR4(sc, MISC_CONTROL_1, MISC_CONTROL_1_DBI_RO_WR_EN);
|
|
|
|
/* Set up bus range. */
|
|
if (OF_getpropintarray(sc->sc_node, "bus-range", bus_range,
|
|
sizeof(bus_range)) != sizeof(bus_range)) {
|
|
bus_range[0] = 0;
|
|
bus_range[1] = 31;
|
|
}
|
|
sc->sc_bus = bus_range[0];
|
|
|
|
/* Initialize bus range. */
|
|
bir = bus_range[0];
|
|
bir |= ((bus_range[0] + 1) << 8);
|
|
bir |= (bus_range[1] << 16);
|
|
HWRITE4(sc, PPB_REG_BUSINFO, bir);
|
|
|
|
/* Initialize memory mapped I/O window. */
|
|
membase = sc->sc_mem_bus_addr;
|
|
memlimit = membase + sc->sc_mem_size - 1;
|
|
blr = memlimit & PPB_MEM_MASK;
|
|
blr |= (membase >> PPB_MEM_SHIFT);
|
|
HWRITE4(sc, PPB_REG_MEM, blr);
|
|
|
|
/* Initialize I/O window. */
|
|
if (sc->sc_io_size > 0) {
|
|
iobase = sc->sc_io_bus_addr;
|
|
iolimit = iobase + sc->sc_io_size - 1;
|
|
blr = iolimit & PPB_IO_MASK;
|
|
blr |= (iobase >> PPB_IO_SHIFT);
|
|
HWRITE4(sc, PPB_REG_IOSTATUS, blr);
|
|
blr = (iobase & 0xffff0000) >> 16;
|
|
blr |= iolimit & 0xffff0000;
|
|
HWRITE4(sc, PPB_REG_IO_HI, blr);
|
|
} else {
|
|
HWRITE4(sc, PPB_REG_IOSTATUS, 0x000000ff);
|
|
HWRITE4(sc, PPB_REG_IO_HI, 0x0000ffff);
|
|
}
|
|
|
|
/* Initialize prefetchable memory mapped I/O window. */
|
|
if (sc->sc_pmem_size > 0) {
|
|
pmembase = sc->sc_pmem_bus_addr;
|
|
pmemlimit = pmembase + sc->sc_pmem_size - 1;
|
|
blr = pmemlimit & PPB_MEM_MASK;
|
|
blr |= ((pmembase & PPB_MEM_MASK) >> PPB_MEM_SHIFT);
|
|
HWRITE4(sc, PPB_REG_PREFMEM, blr);
|
|
HWRITE4(sc, PPB_REG_PREFBASE_HI32, pmembase >> 32);
|
|
HWRITE4(sc, PPB_REG_PREFLIM_HI32, pmemlimit >> 32);
|
|
} else {
|
|
HWRITE4(sc, PPB_REG_PREFMEM, 0x0000ffff);
|
|
HWRITE4(sc, PPB_REG_PREFBASE_HI32, 0);
|
|
HWRITE4(sc, PPB_REG_PREFLIM_HI32, 0);
|
|
}
|
|
|
|
csr = PCI_COMMAND_MASTER_ENABLE | PCI_COMMAND_MEM_ENABLE;
|
|
if (sc->sc_io_size > 0)
|
|
csr |= PCI_COMMAND_IO_ENABLE;
|
|
HWRITE4(sc, PCI_COMMAND_STATUS_REG, csr);
|
|
|
|
memcpy(&sc->sc_bus_iot, sc->sc_iot, sizeof(sc->sc_bus_iot));
|
|
sc->sc_bus_iot.bus_private = sc;
|
|
sc->sc_bus_iot._space_map = dwpcie_bs_iomap;
|
|
memcpy(&sc->sc_bus_memt, sc->sc_iot, sizeof(sc->sc_bus_memt));
|
|
sc->sc_bus_memt.bus_private = sc;
|
|
sc->sc_bus_memt._space_map = dwpcie_bs_memmap;
|
|
|
|
sc->sc_pc.pc_conf_v = sc;
|
|
sc->sc_pc.pc_attach_hook = dwpcie_attach_hook;
|
|
sc->sc_pc.pc_bus_maxdevs = dwpcie_bus_maxdevs;
|
|
sc->sc_pc.pc_make_tag = dwpcie_make_tag;
|
|
sc->sc_pc.pc_decompose_tag = dwpcie_decompose_tag;
|
|
sc->sc_pc.pc_conf_size = dwpcie_conf_size;
|
|
sc->sc_pc.pc_conf_read = dwpcie_conf_read;
|
|
sc->sc_pc.pc_conf_write = dwpcie_conf_write;
|
|
sc->sc_pc.pc_probe_device_hook = dwpcie_probe_device_hook;
|
|
|
|
sc->sc_pc.pc_intr_v = sc;
|
|
sc->sc_pc.pc_intr_map = dwpcie_intr_map;
|
|
sc->sc_pc.pc_intr_map_msi = _pci_intr_map_msi;
|
|
sc->sc_pc.pc_intr_map_msivec = _pci_intr_map_msivec;
|
|
sc->sc_pc.pc_intr_map_msix = _pci_intr_map_msix;
|
|
sc->sc_pc.pc_intr_string = dwpcie_intr_string;
|
|
sc->sc_pc.pc_intr_establish = dwpcie_intr_establish;
|
|
sc->sc_pc.pc_intr_disestablish = dwpcie_intr_disestablish;
|
|
|
|
memset(&pba, 0, sizeof(pba));
|
|
pba.pba_busname = "pci";
|
|
pba.pba_iot = &sc->sc_bus_iot;
|
|
pba.pba_memt = &sc->sc_bus_memt;
|
|
pba.pba_dmat = sc->sc_dmat;
|
|
pba.pba_pc = &sc->sc_pc;
|
|
pba.pba_domain = pci_ndomains++;
|
|
pba.pba_bus = sc->sc_bus;
|
|
|
|
if (OF_is_compatible(sc->sc_node, "baikal,bm1000-pcie") ||
|
|
OF_is_compatible(sc->sc_node, "marvell,armada8k-pcie") ||
|
|
OF_getproplen(sc->sc_node, "msi-map") > 0 ||
|
|
sc->sc_msi_addr)
|
|
pba.pba_flags |= PCI_FLAGS_MSI_ENABLED;
|
|
|
|
/*
|
|
* Only support multiple MSI vectors if we have enough MSI
|
|
* interrupts (or are using an external interrupt controller
|
|
* that hopefully supports plenty of MSI interrupts).
|
|
*/
|
|
if (OF_getproplen(sc->sc_node, "msi-map") > 0 ||
|
|
sc->sc_num_msi > 32)
|
|
pba.pba_flags |= PCI_FLAGS_MSIVEC_ENABLED;
|
|
|
|
pci_dopm = 1;
|
|
|
|
config_found(self, &pba, NULL);
|
|
}
|
|
|
|
int
|
|
dwpcie_get_capability(struct dwpcie_softc *sc, int capid, int *offset,
|
|
pcireg_t *value)
|
|
{
|
|
pcireg_t reg;
|
|
unsigned int ofs;
|
|
|
|
ofs = PCI_CAPLIST_PTR(HREAD4(sc, PCI_CAPLISTPTR_REG));
|
|
while (ofs != 0) {
|
|
reg = HREAD4(sc, ofs);
|
|
if (PCI_CAPLIST_CAP(reg) == capid) {
|
|
if (offset)
|
|
*offset = ofs;
|
|
if (value)
|
|
*value = reg;
|
|
return 1;
|
|
}
|
|
ofs = PCI_CAPLIST_NEXT(reg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
dwpcie_link_config(struct dwpcie_softc *sc)
|
|
{
|
|
uint32_t mode, width, reg;
|
|
int lanes;
|
|
|
|
lanes = OF_getpropint(sc->sc_node, "num-lanes", 0);
|
|
|
|
switch (lanes) {
|
|
case 1:
|
|
mode = PCIE_PORT_LINK_CTRL_LANES_1;
|
|
width = PCIE_LINK_WIDTH_SPEED_CTRL_LANES_1;
|
|
break;
|
|
case 2:
|
|
mode = PCIE_PORT_LINK_CTRL_LANES_2;
|
|
width = PCIE_LINK_WIDTH_SPEED_CTRL_LANES_2;
|
|
break;
|
|
case 4:
|
|
mode = PCIE_PORT_LINK_CTRL_LANES_4;
|
|
width = PCIE_LINK_WIDTH_SPEED_CTRL_LANES_4;
|
|
break;
|
|
case 8:
|
|
mode = PCIE_PORT_LINK_CTRL_LANES_8;
|
|
width = PCIE_LINK_WIDTH_SPEED_CTRL_LANES_8;
|
|
break;
|
|
default:
|
|
printf("%s: %d lanes not supported\n", __func__, lanes);
|
|
return;
|
|
}
|
|
|
|
reg = HREAD4(sc, PCIE_PORT_LINK_CTRL);
|
|
reg &= ~PCIE_PORT_LINK_CTRL_LANES_MASK;
|
|
reg |= mode;
|
|
HWRITE4(sc, PCIE_PORT_LINK_CTRL, reg);
|
|
|
|
reg = HREAD4(sc, PCIE_LINK_WIDTH_SPEED_CTRL);
|
|
reg &= ~PCIE_LINK_WIDTH_SPEED_CTRL_LANES_MASK;
|
|
reg |= width;
|
|
HWRITE4(sc, PCIE_LINK_WIDTH_SPEED_CTRL, reg);
|
|
|
|
reg = HREAD4(sc, PCIE_LINK_WIDTH_SPEED_CTRL);
|
|
reg |= PCIE_LINK_WIDTH_SPEED_CTRL_CHANGE;
|
|
HWRITE4(sc, PCIE_LINK_WIDTH_SPEED_CTRL, reg);
|
|
}
|
|
|
|
int
|
|
dwpcie_msi_intr(struct dwpcie_softc *sc, int idx)
|
|
{
|
|
struct dwpcie_msi *dm;
|
|
uint32_t status;
|
|
int vec, s;
|
|
|
|
status = HREAD4(sc, PCIE_MSI_INTR_STATUS(idx));
|
|
if (status == 0)
|
|
return 0;
|
|
|
|
HWRITE4(sc, PCIE_MSI_INTR_STATUS(idx), status);
|
|
while (status) {
|
|
vec = ffs(status) - 1;
|
|
status &= ~(1U << vec);
|
|
|
|
dm = &sc->sc_msi[idx * 32 + vec];
|
|
if (dm->dm_func == NULL)
|
|
continue;
|
|
|
|
if ((dm->dm_flags & IPL_MPSAFE) == 0)
|
|
KERNEL_LOCK();
|
|
s = splraise(dm->dm_ipl);
|
|
if (dm->dm_func(dm->dm_arg))
|
|
dm->dm_count.ec_count++;
|
|
splx(s);
|
|
if ((dm->dm_flags & IPL_MPSAFE) == 0)
|
|
KERNEL_UNLOCK();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
dwpcie_msi0_intr(void *arg)
|
|
{
|
|
return dwpcie_msi_intr(arg, 0);
|
|
}
|
|
|
|
int
|
|
dwpcie_msi1_intr(void *arg)
|
|
{
|
|
return dwpcie_msi_intr(arg, 1);
|
|
}
|
|
|
|
int
|
|
dwpcie_msi_init(struct dwpcie_softc *sc)
|
|
{
|
|
bus_dma_segment_t seg;
|
|
bus_dmamap_t map;
|
|
uint64_t addr;
|
|
int error, rseg;
|
|
int idx;
|
|
|
|
/*
|
|
* Allocate some DMA memory such that we have a "safe" target
|
|
* address for MSIs.
|
|
*/
|
|
error = bus_dmamem_alloc(sc->sc_dmat, sizeof(uint32_t),
|
|
sizeof(uint32_t), 0, &seg, 1, &rseg, BUS_DMA_WAITOK);
|
|
if (error)
|
|
return error;
|
|
|
|
/*
|
|
* Translate the CPU address into a bus address that we can
|
|
* program into the hardware.
|
|
*/
|
|
error = bus_dmamap_create(sc->sc_dmat, sizeof(uint32_t), 1,
|
|
sizeof(uint32_t), 0, BUS_DMA_WAITOK, &map);
|
|
if (error) {
|
|
bus_dmamem_free(sc->sc_dmat, &seg, 1);
|
|
return error;
|
|
}
|
|
error = bus_dmamap_load_raw(sc->sc_dmat, map, &seg, 1,
|
|
sizeof(uint32_t), BUS_DMA_WAITOK);
|
|
if (error) {
|
|
bus_dmamap_destroy(sc->sc_dmat, map);
|
|
bus_dmamem_free(sc->sc_dmat, &seg, 1);
|
|
return error;
|
|
}
|
|
|
|
addr = map->dm_segs[0].ds_addr;
|
|
HWRITE4(sc, PCIE_MSI_ADDR_LO, addr);
|
|
HWRITE4(sc, PCIE_MSI_ADDR_HI, addr >> 32);
|
|
|
|
bus_dmamap_unload(sc->sc_dmat, map);
|
|
bus_dmamap_destroy(sc->sc_dmat, map);
|
|
|
|
/*
|
|
* See if the device tree indicates that the hardware supports
|
|
* more than 32 vectors. Some hardware supports more than 64,
|
|
* but 64 is good enough for now.
|
|
*/
|
|
idx = OF_getindex(sc->sc_node, "msi1", "interrupt-names");
|
|
if (idx == -1)
|
|
sc->sc_num_msi = 32;
|
|
else
|
|
sc->sc_num_msi = 64;
|
|
KASSERT(sc->sc_num_msi <= DWPCIE_MAX_MSI);
|
|
|
|
/* Enable, mask and clear all MSIs. */
|
|
for (idx = 0; idx < sc->sc_num_msi / 32; idx++) {
|
|
HWRITE4(sc, PCIE_MSI_INTR_ENABLE(idx), 0xffffffff);
|
|
HWRITE4(sc, PCIE_MSI_INTR_MASK(idx), 0xffffffff);
|
|
HWRITE4(sc, PCIE_MSI_INTR_STATUS(idx), 0xffffffff);
|
|
}
|
|
|
|
idx = OF_getindex(sc->sc_node, "msi0", "interrupt-names");
|
|
if (idx == -1)
|
|
idx = 0;
|
|
|
|
sc->sc_msi_ih[0] = fdt_intr_establish_idx(sc->sc_node, idx,
|
|
IPL_BIO | IPL_MPSAFE, dwpcie_msi0_intr, sc, sc->sc_dev.dv_xname);
|
|
if (sc->sc_msi_ih[0] == NULL) {
|
|
bus_dmamem_free(sc->sc_dmat, &seg, 1);
|
|
return EINVAL;
|
|
}
|
|
|
|
idx = OF_getindex(sc->sc_node, "msi1", "interrupt-names");
|
|
if (idx == -1)
|
|
goto finish;
|
|
|
|
sc->sc_msi_ih[1] = fdt_intr_establish_idx(sc->sc_node, idx,
|
|
IPL_BIO | IPL_MPSAFE, dwpcie_msi1_intr, sc, sc->sc_dev.dv_xname);
|
|
if (sc->sc_msi_ih[1] == NULL)
|
|
sc->sc_num_msi = 32;
|
|
|
|
finish:
|
|
/*
|
|
* Hold on to the DMA memory such that nobody can use it to
|
|
* actually do DMA transfers.
|
|
*/
|
|
|
|
sc->sc_msi_addr = addr;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dwpcie_armada8k_init(struct dwpcie_softc *sc)
|
|
{
|
|
uint32_t reg;
|
|
int timo;
|
|
|
|
clock_enable_all(sc->sc_node);
|
|
|
|
dwpcie_link_config(sc);
|
|
|
|
if (!dwpcie_armada8k_link_up(sc)) {
|
|
reg = HREAD4(sc, PCIE_GLOBAL_CTRL);
|
|
reg &= ~PCIE_GLOBAL_CTRL_APP_LTSSM_EN;
|
|
HWRITE4(sc, PCIE_GLOBAL_CTRL, reg);
|
|
}
|
|
|
|
/*
|
|
* Setup Requester-ID to Stream-ID mapping
|
|
* XXX: TF-A is supposed to set this up, but doesn't!
|
|
*/
|
|
HWRITE4(sc, PCIE_STREAMID, PCIE_STREAMID_8040);
|
|
|
|
/* Enable Root Complex mode. */
|
|
reg = HREAD4(sc, PCIE_GLOBAL_CTRL);
|
|
reg &= ~PCIE_GLOBAL_CTRL_DEVICE_TYPE_MASK;
|
|
reg |= PCIE_GLOBAL_CTRL_DEVICE_TYPE_RC;
|
|
HWRITE4(sc, PCIE_GLOBAL_CTRL, reg);
|
|
|
|
HWRITE4(sc, PCIE_ARCACHE_TRC, PCIE_ARCACHE_TRC_DEFAULT);
|
|
HWRITE4(sc, PCIE_AWCACHE_TRC, PCIE_AWCACHE_TRC_DEFAULT);
|
|
reg = HREAD4(sc, PCIE_ARUSER);
|
|
reg &= ~PCIE_AXUSER_DOMAIN_MASK;
|
|
reg |= PCIE_AXUSER_DOMAIN_OUTER_SHARABLE;
|
|
HWRITE4(sc, PCIE_ARUSER, reg);
|
|
reg = HREAD4(sc, PCIE_AWUSER);
|
|
reg &= ~PCIE_AXUSER_DOMAIN_MASK;
|
|
reg |= PCIE_AXUSER_DOMAIN_OUTER_SHARABLE;
|
|
HWRITE4(sc, PCIE_AWUSER, reg);
|
|
|
|
if (!dwpcie_armada8k_link_up(sc)) {
|
|
reg = HREAD4(sc, PCIE_GLOBAL_CTRL);
|
|
reg |= PCIE_GLOBAL_CTRL_APP_LTSSM_EN;
|
|
HWRITE4(sc, PCIE_GLOBAL_CTRL, reg);
|
|
}
|
|
|
|
for (timo = 40; timo > 0; timo--) {
|
|
if (dwpcie_armada8k_link_up(sc))
|
|
break;
|
|
delay(1000);
|
|
}
|
|
if (timo == 0)
|
|
return ETIMEDOUT;
|
|
|
|
sc->sc_ih = fdt_intr_establish(sc->sc_node, IPL_AUDIO | IPL_MPSAFE,
|
|
dwpcie_armada8k_intr, sc, sc->sc_dev.dv_xname);
|
|
|
|
/* Unmask INTx interrupts. */
|
|
HWRITE4(sc, PCIE_GLOBAL_INT_MASK,
|
|
PCIE_GLOBAL_INT_MASK_INT_A | PCIE_GLOBAL_INT_MASK_INT_B |
|
|
PCIE_GLOBAL_INT_MASK_INT_C | PCIE_GLOBAL_INT_MASK_INT_D);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dwpcie_armada8k_link_up(struct dwpcie_softc *sc)
|
|
{
|
|
uint32_t reg, mask;
|
|
|
|
mask = PCIE_GLOBAL_STATUS_RDLH_LINK_UP;
|
|
mask |= PCIE_GLOBAL_STATUS_PHY_LINK_UP;
|
|
reg = HREAD4(sc, PCIE_GLOBAL_STATUS);
|
|
return ((reg & mask) == mask);
|
|
}
|
|
|
|
int
|
|
dwpcie_armada8k_intr(void *arg)
|
|
{
|
|
struct dwpcie_softc *sc = arg;
|
|
uint32_t cause;
|
|
|
|
/* Acknowledge interrupts. */
|
|
cause = HREAD4(sc, PCIE_GLOBAL_INT_CAUSE);
|
|
HWRITE4(sc, PCIE_GLOBAL_INT_CAUSE, cause);
|
|
|
|
/* INTx interrupt, so not really ours. */
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dwpcie_g12a_init(struct dwpcie_softc *sc)
|
|
{
|
|
uint32_t *reset_gpio;
|
|
ssize_t reset_gpiolen;
|
|
uint32_t reg;
|
|
int error, timo;
|
|
|
|
reset_gpiolen = OF_getproplen(sc->sc_node, "reset-gpios");
|
|
if (reset_gpiolen <= 0)
|
|
return ENXIO;
|
|
|
|
if (bus_space_map(sc->sc_iot, sc->sc_glue_base,
|
|
sc->sc_glue_size, 0, &sc->sc_glue_ioh))
|
|
return ENOMEM;
|
|
|
|
power_domain_enable(sc->sc_node);
|
|
|
|
phy_enable(sc->sc_node, "pcie");
|
|
|
|
reset_assert_all(sc->sc_node);
|
|
delay(500);
|
|
reset_deassert_all(sc->sc_node);
|
|
delay(500);
|
|
|
|
clock_set_frequency(sc->sc_node, "port", 100000000UL);
|
|
clock_enable_all(sc->sc_node);
|
|
|
|
reset_gpio = malloc(reset_gpiolen, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(sc->sc_node, "reset-gpios", reset_gpio,
|
|
reset_gpiolen);
|
|
gpio_controller_config_pin(reset_gpio, GPIO_CONFIG_OUTPUT);
|
|
gpio_controller_set_pin(reset_gpio, 1);
|
|
|
|
dwpcie_link_config(sc);
|
|
|
|
reg = bus_space_read_4(sc->sc_iot, sc->sc_glue_ioh, PCIE_CFG0);
|
|
reg |= PCIE_CFG0_APP_LTSSM_EN;
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh, PCIE_CFG0, reg);
|
|
|
|
gpio_controller_set_pin(reset_gpio, 1);
|
|
delay(500);
|
|
gpio_controller_set_pin(reset_gpio, 0);
|
|
|
|
free(reset_gpio, M_TEMP, reset_gpiolen);
|
|
|
|
for (timo = 40; timo > 0; timo--) {
|
|
if (dwpcie_g12a_link_up(sc))
|
|
break;
|
|
delay(1000);
|
|
}
|
|
if (timo == 0)
|
|
return ETIMEDOUT;
|
|
|
|
error = dwpcie_msi_init(sc);
|
|
if (error)
|
|
return error;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dwpcie_g12a_link_up(struct dwpcie_softc *sc)
|
|
{
|
|
uint32_t reg;
|
|
|
|
reg = bus_space_read_4(sc->sc_iot, sc->sc_glue_ioh, PCIE_STATUS12);
|
|
if ((reg & PCIE_STATUS12_SMLH_LINK_UP) &&
|
|
(reg & PCIE_STATUS12_RDLH_LINK_UP) &&
|
|
(reg & PCIE_STATUS12_LTSSM_MASK) == PCIE_STATUS12_LTSSM_UP)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dwpcie_imx8mq_init(struct dwpcie_softc *sc)
|
|
{
|
|
uint32_t *clkreq_gpio, *disable_gpio, *reset_gpio;
|
|
ssize_t clkreq_gpiolen, disable_gpiolen, reset_gpiolen;
|
|
struct regmap *anatop, *gpr, *phy;
|
|
uint32_t off, reg;
|
|
int error, timo;
|
|
|
|
if (OF_is_compatible(sc->sc_node, "fsl,imx8mm-pcie")) {
|
|
anatop = regmap_bycompatible("fsl,imx8mm-anatop");
|
|
gpr = regmap_bycompatible("fsl,imx8mm-iomuxc-gpr");
|
|
phy = regmap_bycompatible("fsl,imx7d-pcie-phy");
|
|
KASSERT(phy != NULL);
|
|
} else {
|
|
anatop = regmap_bycompatible("fsl,imx8mq-anatop");
|
|
gpr = regmap_bycompatible("fsl,imx8mq-iomuxc-gpr");
|
|
}
|
|
KASSERT(anatop != NULL);
|
|
KASSERT(gpr != NULL);
|
|
|
|
clkreq_gpiolen = OF_getproplen(sc->sc_node, "clkreq-gpio");
|
|
disable_gpiolen = OF_getproplen(sc->sc_node, "disable-gpio");
|
|
reset_gpiolen = OF_getproplen(sc->sc_node, "reset-gpio");
|
|
|
|
if (clkreq_gpiolen > 0) {
|
|
clkreq_gpio = malloc(clkreq_gpiolen, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(sc->sc_node, "clkreq-gpio", clkreq_gpio,
|
|
clkreq_gpiolen);
|
|
gpio_controller_config_pin(clkreq_gpio, GPIO_CONFIG_OUTPUT);
|
|
gpio_controller_set_pin(clkreq_gpio, 1);
|
|
}
|
|
|
|
if (disable_gpiolen > 0) {
|
|
disable_gpio = malloc(disable_gpiolen, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(sc->sc_node, "disable-gpio", disable_gpio,
|
|
disable_gpiolen);
|
|
gpio_controller_config_pin(disable_gpio, GPIO_CONFIG_OUTPUT);
|
|
gpio_controller_set_pin(disable_gpio, 0);
|
|
}
|
|
|
|
if (reset_gpiolen > 0) {
|
|
reset_gpio = malloc(reset_gpiolen, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(sc->sc_node, "reset-gpio", reset_gpio,
|
|
reset_gpiolen);
|
|
gpio_controller_config_pin(reset_gpio, GPIO_CONFIG_OUTPUT);
|
|
gpio_controller_set_pin(reset_gpio, 1);
|
|
}
|
|
|
|
power_domain_enable(sc->sc_node);
|
|
reset_assert(sc->sc_node, "pciephy");
|
|
reset_assert(sc->sc_node, "apps");
|
|
|
|
reg = regmap_read_4(gpr, IOMUXC_GPR12);
|
|
if (OF_getpropint(sc->sc_node, "ctrl-id", 0) == 0) {
|
|
off = IOMUXC_GPR14;
|
|
reg &= ~IMX8MQ_GPR_PCIE1_DEVICE_TYPE_MASK;
|
|
reg |= IMX8MQ_GPR_PCIE1_DEVICE_TYPE_RC;
|
|
} else {
|
|
off = IOMUXC_GPR16;
|
|
reg &= ~IMX8MQ_GPR_PCIE2_DEVICE_TYPE_MASK;
|
|
reg |= IMX8MQ_GPR_PCIE2_DEVICE_TYPE_RC;
|
|
}
|
|
regmap_write_4(gpr, IOMUXC_GPR12, reg);
|
|
|
|
if (OF_is_compatible(sc->sc_node, "fsl,imx8mm-pcie")) {
|
|
if (OF_getproplen(sc->sc_node, "ext_osc") == 0 ||
|
|
OF_getpropint(sc->sc_node, "ext_osc", 0)) {
|
|
reg = regmap_read_4(gpr, off);
|
|
reg &= ~(IMX8MQ_GPR_PCIE_REF_USE_PAD |
|
|
IMX8MM_GPR_PCIE_SSC_EN |
|
|
IMX8MM_GPR_PCIE_POWER_OFF |
|
|
IMX8MM_GPR_PCIE_REF_CLK_MASK);
|
|
reg |= (IMX8MM_GPR_PCIE_AUX_EN |
|
|
IMX8MM_GPR_PCIE_REF_CLK_EXT);
|
|
regmap_write_4(gpr, off, reg);
|
|
delay(100);
|
|
reg = regmap_read_4(gpr, off);
|
|
reg |= IMX8MM_GPR_PCIE_CMN_RST;
|
|
regmap_write_4(gpr, off, reg);
|
|
delay(200);
|
|
} else {
|
|
reg = regmap_read_4(gpr, off);
|
|
reg &= ~(IMX8MQ_GPR_PCIE_REF_USE_PAD |
|
|
IMX8MM_GPR_PCIE_SSC_EN |
|
|
IMX8MM_GPR_PCIE_POWER_OFF |
|
|
IMX8MM_GPR_PCIE_REF_CLK_MASK);
|
|
reg |= (IMX8MM_GPR_PCIE_AUX_EN |
|
|
IMX8MM_GPR_PCIE_REF_CLK_PLL);
|
|
regmap_write_4(gpr, off, reg);
|
|
delay(100);
|
|
regmap_write_4(phy, IMX8MM_PCIE_PHY_CMN_REG62,
|
|
IMX8MM_PCIE_PHY_CMN_REG62_PLL_CLK_OUT);
|
|
regmap_write_4(phy, IMX8MM_PCIE_PHY_CMN_REG64,
|
|
IMX8MM_PCIE_PHY_CMN_REG64_AUX_RX_TX_TERM);
|
|
reg = regmap_read_4(gpr, off);
|
|
reg |= IMX8MM_GPR_PCIE_CMN_RST;
|
|
regmap_write_4(gpr, off, reg);
|
|
delay(200);
|
|
regmap_write_4(phy, IMX8MM_PCIE_PHY_TRSV_REG5,
|
|
IMX8MM_PCIE_PHY_TRSV_REG5_GEN1_DEEMP);
|
|
regmap_write_4(phy, IMX8MM_PCIE_PHY_TRSV_REG6,
|
|
IMX8MM_PCIE_PHY_TRSV_REG6_GEN2_DEEMP);
|
|
}
|
|
} else {
|
|
if (OF_getproplen(sc->sc_node, "ext_osc") == 0 ||
|
|
OF_getpropint(sc->sc_node, "ext_osc", 0)) {
|
|
reg = regmap_read_4(gpr, off);
|
|
reg |= IMX8MQ_GPR_PCIE_REF_USE_PAD;
|
|
regmap_write_4(gpr, off, reg);
|
|
} else {
|
|
reg = regmap_read_4(gpr, off);
|
|
reg &= ~IMX8MQ_GPR_PCIE_REF_USE_PAD;
|
|
regmap_write_4(gpr, off, reg);
|
|
|
|
regmap_write_4(anatop, ANATOP_PLLOUT_CTL,
|
|
ANATOP_PLLOUT_CTL_CKE |
|
|
ANATOP_PLLOUT_CTL_SEL_SYSPLL1);
|
|
regmap_write_4(anatop, ANATOP_PLLOUT_DIV,
|
|
ANATOP_PLLOUT_DIV_SYSPLL1);
|
|
}
|
|
}
|
|
|
|
clock_enable(sc->sc_node, "pcie_phy");
|
|
clock_enable(sc->sc_node, "pcie_bus");
|
|
clock_enable(sc->sc_node, "pcie");
|
|
clock_enable(sc->sc_node, "pcie_aux");
|
|
|
|
/* Allow clocks to stabilize. */
|
|
delay(200);
|
|
|
|
if (reset_gpiolen > 0) {
|
|
gpio_controller_set_pin(reset_gpio, 1);
|
|
delay(100000);
|
|
gpio_controller_set_pin(reset_gpio, 0);
|
|
}
|
|
|
|
reset_deassert(sc->sc_node, "pciephy");
|
|
|
|
if (OF_is_compatible(sc->sc_node, "fsl,imx8mm-pcie")) {
|
|
for (timo = 2000; timo > 0; timo--) {
|
|
if (regmap_read_4(phy, IMX8MM_PCIE_PHY_CMN_REG75) ==
|
|
IMX8MM_PCIE_PHY_CMN_REG75_PLL_DONE)
|
|
break;
|
|
delay(10);
|
|
}
|
|
if (timo == 0) {
|
|
error = ETIMEDOUT;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
reg = HREAD4(sc, 0x100000 + PCIE_RC_LCR);
|
|
reg &= ~PCIE_RC_LCR_L1EL_MASK;
|
|
reg |= PCIE_RC_LCR_L1EL_64US;
|
|
HWRITE4(sc, 0x100000 + PCIE_RC_LCR, reg);
|
|
|
|
dwpcie_link_config(sc);
|
|
|
|
reg = HREAD4(sc, PCIE_RC_LCR);
|
|
reg &= ~PCIE_RC_LCR_MAX_LINK_SPEEDS_MASK;
|
|
reg |= PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN1;
|
|
HWRITE4(sc, PCIE_RC_LCR, reg);
|
|
|
|
reset_deassert(sc->sc_node, "apps");
|
|
|
|
for (timo = 20000; timo > 0; timo--) {
|
|
if (dwpcie_link_up(sc))
|
|
break;
|
|
delay(10);
|
|
}
|
|
if (timo == 0) {
|
|
error = ETIMEDOUT;
|
|
goto err;
|
|
}
|
|
|
|
if (OF_getpropint(sc->sc_node, "fsl,max-link-speed", 1) >= 2) {
|
|
reg = HREAD4(sc, PCIE_RC_LCR);
|
|
reg &= ~PCIE_RC_LCR_MAX_LINK_SPEEDS_MASK;
|
|
reg |= PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN2;
|
|
HWRITE4(sc, PCIE_RC_LCR, reg);
|
|
|
|
reg = HREAD4(sc, PCIE_LINK_WIDTH_SPEED_CTRL);
|
|
reg |= PCIE_LINK_WIDTH_SPEED_CTRL_CHANGE;
|
|
HWRITE4(sc, PCIE_LINK_WIDTH_SPEED_CTRL, reg);
|
|
|
|
for (timo = 20000; timo > 0; timo--) {
|
|
if (dwpcie_link_up(sc))
|
|
break;
|
|
delay(10);
|
|
}
|
|
if (timo == 0) {
|
|
error = ETIMEDOUT;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
sc->sc_ih = fdt_intr_establish(sc->sc_node, IPL_AUDIO | IPL_MPSAFE,
|
|
dwpcie_imx8mq_intr, sc, sc->sc_dev.dv_xname);
|
|
|
|
/* Unmask INTx interrupts. */
|
|
HWRITE4(sc, PCIE_GLOBAL_INT_MASK,
|
|
PCIE_GLOBAL_INT_MASK_INT_A | PCIE_GLOBAL_INT_MASK_INT_B |
|
|
PCIE_GLOBAL_INT_MASK_INT_C | PCIE_GLOBAL_INT_MASK_INT_D);
|
|
|
|
error = 0;
|
|
err:
|
|
if (clkreq_gpiolen > 0)
|
|
free(clkreq_gpio, M_TEMP, clkreq_gpiolen);
|
|
if (disable_gpiolen > 0)
|
|
free(disable_gpio, M_TEMP, disable_gpiolen);
|
|
if (reset_gpiolen > 0)
|
|
free(reset_gpio, M_TEMP, reset_gpiolen);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
dwpcie_imx8mq_intr(void *arg)
|
|
{
|
|
struct dwpcie_softc *sc = arg;
|
|
uint32_t cause;
|
|
|
|
/* Acknowledge interrupts. */
|
|
cause = HREAD4(sc, PCIE_GLOBAL_INT_CAUSE);
|
|
HWRITE4(sc, PCIE_GLOBAL_INT_CAUSE, cause);
|
|
|
|
/* INTx interrupt, so not really ours. */
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dwpcie_fu740_init(struct dwpcie_softc *sc)
|
|
{
|
|
sc->sc_num_viewport = 8;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* SpacemiT K1 registers */
|
|
#define APMU_PCIE_CLK_RES_CTRL 0x0000
|
|
#define APMU_PCIE_DEVICE_TYPE_SEL (1U << 31)
|
|
#define APMU_PCIE_APP_HOLD_PHY_RST (1U << 30)
|
|
#define APMU_PCIE_RC_PERST (1U << 12)
|
|
#define APMU_PCIE_SYS_AUX_PWR_DET (1U << 9)
|
|
#define APMU_PCIE_LTSSM_EN (1U << 6)
|
|
#define APMU_PCIE_CTRL_LOGIC 0x0004
|
|
#define APMU_PCIE_SOFT_RESET (1U << 0)
|
|
|
|
#define K1_PHY_AHB_IRQ 0x0000
|
|
#define K1_PHY_AHB_IRQ_EN (1U << 0)
|
|
#define K1_PHY_AHB_IRQENABLE_MSI 0x0014
|
|
#define K1_PHY_AHB_IRQENABLE_MSI_EN (1U << 11)
|
|
|
|
int
|
|
dwpcie_k1_init(struct dwpcie_softc *sc)
|
|
{
|
|
struct regmap *apmu_rm;
|
|
uint32_t apmu[2], val;
|
|
int error, off, timo;
|
|
|
|
sc->sc_num_viewport = 8;
|
|
|
|
if (bus_space_map(sc->sc_iot, sc->sc_glue_base,
|
|
sc->sc_glue_size, 0, &sc->sc_glue_ioh))
|
|
return ENOMEM;
|
|
|
|
if (OF_getpropintarray(sc->sc_node, "spacemit,apmu",
|
|
apmu, sizeof(apmu)) != sizeof(apmu))
|
|
return EINVAL;
|
|
|
|
apmu_rm = regmap_byphandle(apmu[0]);
|
|
if (apmu_rm == NULL)
|
|
return ENXIO;
|
|
|
|
/* Hold PHY in reset until starting the link. */
|
|
val = regmap_read_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL);
|
|
val |= APMU_PCIE_APP_HOLD_PHY_RST;
|
|
val &= ~APMU_PCIE_LTSSM_EN;
|
|
regmap_write_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL, val);
|
|
|
|
/* Soft reset. */
|
|
val = regmap_read_4(apmu_rm, apmu[1] + APMU_PCIE_CTRL_LOGIC);
|
|
regmap_write_4(apmu_rm, apmu[1] + APMU_PCIE_CTRL_LOGIC,
|
|
val | APMU_PCIE_SOFT_RESET);
|
|
regmap_read_4(apmu_rm, apmu[1] + APMU_PCIE_CTRL_LOGIC);
|
|
delay(2000);
|
|
regmap_write_4(apmu_rm, apmu[1] + APMU_PCIE_CTRL_LOGIC,
|
|
val &= ~APMU_PCIE_SOFT_RESET);
|
|
|
|
clock_enable_all(sc->sc_node);
|
|
reset_deassert_all(sc->sc_node);
|
|
|
|
/* Set vendor ID and product ID. (*/
|
|
HSET4(sc, MISC_CONTROL_1, MISC_CONTROL_1_DBI_RO_WR_EN);
|
|
HWRITE4(sc, PCI_ID_REG,
|
|
PCI_VENDOR_SPACEMIT << PCI_VENDOR_SHIFT |
|
|
PCI_PRODUCT_SPACEMIT_K1 << PCI_PRODUCT_SHIFT);
|
|
HCLR4(sc, MISC_CONTROL_1, MISC_CONTROL_1_DBI_RO_WR_EN);
|
|
|
|
/* Assert PERST#. */
|
|
val = regmap_read_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL);
|
|
regmap_write_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL,
|
|
val | APMU_PCIE_RC_PERST);
|
|
regmap_read_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL);
|
|
delay(100000);
|
|
|
|
/* Set RC mode; indicate Vaux presence. */
|
|
val = regmap_read_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL);
|
|
regmap_write_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL,
|
|
val | APMU_PCIE_DEVICE_TYPE_SEL | APMU_PCIE_SYS_AUX_PWR_DET);
|
|
|
|
phy_enable_idx(OF_child(sc->sc_node), 0);
|
|
|
|
/* Deassert PERST#. */
|
|
val = regmap_read_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL);
|
|
regmap_write_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL,
|
|
val & ~APMU_PCIE_RC_PERST);
|
|
|
|
/* Disable ASPM L1. */
|
|
if (!dwpcie_get_capability(sc, PCI_CAP_PCIEXPRESS, &off, NULL))
|
|
return ENXIO;
|
|
HSET4(sc, MISC_CONTROL_1, MISC_CONTROL_1_DBI_RO_WR_EN);
|
|
val = HREAD4(sc, off + PCI_PCIE_LCAP);
|
|
val &= ~PCI_PCIE_LCAP_ASPM_L1;
|
|
HWRITE4(sc, off + PCI_PCIE_LCAP, val);
|
|
HCLR4(sc, MISC_CONTROL_1, MISC_CONTROL_1_DBI_RO_WR_EN);
|
|
|
|
error = dwpcie_msi_init(sc);
|
|
if (error)
|
|
return error;
|
|
|
|
val = regmap_read_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL);
|
|
val &= ~APMU_PCIE_APP_HOLD_PHY_RST;
|
|
val |= APMU_PCIE_LTSSM_EN;
|
|
regmap_write_4(apmu_rm, apmu[1] + APMU_PCIE_CLK_RES_CTRL, val);
|
|
|
|
/* Enable MSIs */
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh,
|
|
K1_PHY_AHB_IRQENABLE_MSI, K1_PHY_AHB_IRQENABLE_MSI_EN);
|
|
val = bus_space_read_4(sc->sc_iot, sc->sc_glue_ioh, K1_PHY_AHB_IRQ);
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh, K1_PHY_AHB_IRQ,
|
|
val | K1_PHY_AHB_IRQ_EN);
|
|
|
|
/* Wait for the link to come up. */
|
|
for (timo = 20000; timo > 0; timo--) {
|
|
if (dwpcie_link_up(sc))
|
|
break;
|
|
delay(10);
|
|
}
|
|
if (timo == 0)
|
|
return ETIMEDOUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dwpcie_rk3568_link_up(struct dwpcie_softc *sc)
|
|
{
|
|
uint32_t reg;
|
|
|
|
reg = bus_space_read_4(sc->sc_iot, sc->sc_glue_ioh,
|
|
PCIE_CLIENT_LTSSM_STATUS);
|
|
if ((reg & PCIE_CLIENT_SMLH_LINK_UP) &&
|
|
(reg & PCIE_CLIENT_RDLH_LINK_UP) &&
|
|
(reg & PCIE_CLIENT_LTSSM_MASK) == PCIE_CLIENT_LTSSM_UP)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dwpcie_rk3568_init(struct dwpcie_softc *sc)
|
|
{
|
|
uint32_t *reset_gpio;
|
|
ssize_t reset_gpiolen;
|
|
int error, idx, node;
|
|
int pin, timo;
|
|
|
|
sc->sc_num_viewport = 8;
|
|
|
|
if (bus_space_map(sc->sc_iot, sc->sc_glue_base,
|
|
sc->sc_glue_size, 0, &sc->sc_glue_ioh))
|
|
return ENOMEM;
|
|
|
|
reset_assert_all(sc->sc_node);
|
|
/* Power must be enabled before initializing the PHY. */
|
|
regulator_enable(OF_getpropint(sc->sc_node, "vpcie3v3-supply", 0));
|
|
phy_enable(sc->sc_node, "pcie-phy");
|
|
reset_deassert_all(sc->sc_node);
|
|
|
|
clock_enable_all(sc->sc_node);
|
|
|
|
if (dwpcie_rk3568_link_up(sc))
|
|
return 0;
|
|
|
|
reset_gpiolen = OF_getproplen(sc->sc_node, "reset-gpios");
|
|
if (reset_gpiolen > 0) {
|
|
reset_gpio = malloc(reset_gpiolen, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(sc->sc_node, "reset-gpios", reset_gpio,
|
|
reset_gpiolen);
|
|
gpio_controller_config_pin(reset_gpio, GPIO_CONFIG_OUTPUT);
|
|
gpio_controller_set_pin(reset_gpio, 1);
|
|
}
|
|
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh,
|
|
PCIE_CLIENT_HOT_RESET_CTRL, PCIE_CLIENT_APP_LTSSM_ENABLE_ENHANCE);
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh,
|
|
PCIE_CLIENT_GENERAL_CON, PCIE_CLIENT_DEV_TYPE_RC);
|
|
|
|
/* Assert PERST#. */
|
|
if (reset_gpiolen > 0)
|
|
gpio_controller_set_pin(reset_gpio, 0);
|
|
|
|
dwpcie_link_config(sc);
|
|
|
|
/* Enable LTSSM. */
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh, PCIE_CLIENT_GENERAL_CON,
|
|
PCIE_CLIENT_LINK_REQ_RST_GRT | PCIE_CLIENT_APP_LTSSM_ENABLE);
|
|
|
|
/*
|
|
* PERST# must remain asserted for at least 100us after the
|
|
* reference clock becomes stable. But also has to remain
|
|
* active at least 100ms after power up. Since we may have
|
|
* just powered on the device, play it safe and use 100ms.
|
|
*/
|
|
delay(100000);
|
|
|
|
/* Deassert PERST#. */
|
|
if (reset_gpiolen > 0)
|
|
gpio_controller_set_pin(reset_gpio, 1);
|
|
|
|
/* Wait for the link to come up. */
|
|
for (timo = 100; timo > 0; timo--) {
|
|
if (dwpcie_rk3568_link_up(sc))
|
|
break;
|
|
delay(10000);
|
|
}
|
|
if (timo == 0) {
|
|
error = ETIMEDOUT;
|
|
goto err;
|
|
}
|
|
|
|
node = OF_getnodebyname(sc->sc_node, "legacy-interrupt-controller");
|
|
idx = OF_getindex(sc->sc_node, "legacy", "interrupt-names");
|
|
if (node && idx != -1) {
|
|
sc->sc_ih = fdt_intr_establish_idx(sc->sc_node, idx,
|
|
IPL_BIO | IPL_MPSAFE, dwpcie_rk3568_intr, sc,
|
|
sc->sc_dev.dv_xname);
|
|
}
|
|
|
|
if (sc->sc_ih) {
|
|
for (pin = 0; pin < nitems(sc->sc_intx); pin++)
|
|
TAILQ_INIT(&sc->sc_intx[pin]);
|
|
sc->sc_ic.ic_node = node;
|
|
sc->sc_ic.ic_cookie = sc;
|
|
sc->sc_ic.ic_establish = dwpcie_rk3568_intr_establish;
|
|
sc->sc_ic.ic_disestablish = dwpcie_rk3568_intr_disestablish;
|
|
sc->sc_ic.ic_barrier = dwpcie_rk3568_intr_barrier;
|
|
fdt_intr_register(&sc->sc_ic);
|
|
}
|
|
|
|
error = 0;
|
|
err:
|
|
if (reset_gpiolen > 0)
|
|
free(reset_gpio, M_TEMP, reset_gpiolen);
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
dwpcie_rk3568_intr(void *arg)
|
|
{
|
|
struct dwpcie_softc *sc = arg;
|
|
struct dwpcie_intx *di;
|
|
uint32_t status;
|
|
int pin, s;
|
|
|
|
status = bus_space_read_4(sc->sc_iot, sc->sc_glue_ioh,
|
|
PCIE_CLIENT_INTR_STATUS_LEGACY);
|
|
for (pin = 0; pin < nitems(sc->sc_intx); pin++) {
|
|
if ((status & (1 << pin)) == 0)
|
|
continue;
|
|
|
|
TAILQ_FOREACH(di, &sc->sc_intx[pin], di_next) {
|
|
if ((di->di_flags & IPL_MPSAFE) == 0)
|
|
KERNEL_LOCK();
|
|
s = splraise(di->di_ipl);
|
|
if (di->di_func(di->di_arg))
|
|
di->di_count.ec_count++;
|
|
splx(s);
|
|
if ((di->di_flags & IPL_MPSAFE) == 0)
|
|
KERNEL_UNLOCK();
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void *
|
|
dwpcie_rk3568_intr_establish(void *cookie, int *cell, int level,
|
|
struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
|
|
{
|
|
struct dwpcie_softc *sc = (struct dwpcie_softc *)cookie;
|
|
struct dwpcie_intx *di;
|
|
int pin = cell[0];
|
|
uint32_t mask = (1U << pin);
|
|
|
|
if (ci != NULL && !CPU_IS_PRIMARY(ci))
|
|
return NULL;
|
|
|
|
if (pin < 0 || pin >= nitems(sc->sc_intx))
|
|
return NULL;
|
|
|
|
/* Mask the interrupt. */
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh,
|
|
PCIE_CLIENT_INTR_MASK_LEGACY, (mask << 16) | mask);
|
|
intr_barrier(sc->sc_ih);
|
|
|
|
di = malloc(sizeof(*di), M_DEVBUF, M_WAITOK | M_ZERO);
|
|
di->di_func = func;
|
|
di->di_arg = arg;
|
|
di->di_ipl = level & IPL_IRQMASK;
|
|
di->di_flags = level & IPL_FLAGMASK;
|
|
di->di_pin = pin;
|
|
di->di_name = name;
|
|
if (name != NULL)
|
|
evcount_attach(&di->di_count, name, &di->di_pin);
|
|
di->di_sc = sc;
|
|
TAILQ_INSERT_TAIL(&sc->sc_intx[pin], di, di_next);
|
|
|
|
/* Unmask the interrupt. */
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh,
|
|
PCIE_CLIENT_INTR_MASK_LEGACY, mask << 16);
|
|
|
|
return di;
|
|
}
|
|
|
|
void
|
|
dwpcie_rk3568_intr_disestablish(void *cookie)
|
|
{
|
|
struct dwpcie_intx *di = cookie;
|
|
struct dwpcie_softc *sc = di->di_sc;
|
|
uint32_t mask = (1U << di->di_pin);
|
|
|
|
/* Mask the interrupt. */
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh,
|
|
PCIE_CLIENT_INTR_MASK_LEGACY, (mask << 16) | mask);
|
|
intr_barrier(sc->sc_ih);
|
|
|
|
if (di->di_name)
|
|
evcount_detach(&di->di_count);
|
|
|
|
TAILQ_REMOVE(&sc->sc_intx[di->di_pin], di, di_next);
|
|
|
|
if (!TAILQ_EMPTY(&sc->sc_intx[di->di_pin])) {
|
|
/* Unmask the interrupt. */
|
|
bus_space_write_4(sc->sc_iot, sc->sc_glue_ioh,
|
|
PCIE_CLIENT_INTR_MASK_LEGACY, mask << 16);
|
|
}
|
|
|
|
free(di, M_DEVBUF, sizeof(*di));
|
|
}
|
|
|
|
void
|
|
dwpcie_rk3568_intr_barrier(void *cookie)
|
|
{
|
|
struct dwpcie_intx *di = cookie;
|
|
struct dwpcie_softc *sc = di->di_sc;
|
|
|
|
intr_barrier(sc->sc_ih);
|
|
}
|
|
|
|
int
|
|
dwpcie_sc7280_init(struct dwpcie_softc *sc)
|
|
{
|
|
sc->sc_num_viewport = 8;
|
|
|
|
if (OF_getproplen(sc->sc_node, "msi-map") <= 0)
|
|
return dwpcie_msi_init(sc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
dwpcie_atu_write(struct dwpcie_softc *sc, int index, off_t reg,
|
|
uint32_t val)
|
|
{
|
|
if (sc->sc_atu_unroll) {
|
|
bus_space_write_4(sc->sc_iot, sc->sc_atu_ioh,
|
|
IATU_OFFSET_UNROLL(index) + reg, val);
|
|
return;
|
|
}
|
|
|
|
if (sc->sc_atu_viewport != index) {
|
|
HWRITE4(sc, IATU_VIEWPORT, index);
|
|
sc->sc_atu_viewport = index;
|
|
}
|
|
|
|
HWRITE4(sc, IATU_OFFSET_VIEWPORT + reg, val);
|
|
}
|
|
|
|
uint32_t
|
|
dwpcie_atu_read(struct dwpcie_softc *sc, int index, off_t reg)
|
|
{
|
|
if (sc->sc_atu_unroll) {
|
|
return bus_space_read_4(sc->sc_iot, sc->sc_atu_ioh,
|
|
IATU_OFFSET_UNROLL(index) + reg);
|
|
}
|
|
|
|
if (sc->sc_atu_viewport != index) {
|
|
HWRITE4(sc, IATU_VIEWPORT, index);
|
|
sc->sc_atu_viewport = index;
|
|
}
|
|
|
|
return HREAD4(sc, IATU_OFFSET_VIEWPORT + reg);
|
|
}
|
|
|
|
void
|
|
dwpcie_atu_disable(struct dwpcie_softc *sc, int index)
|
|
{
|
|
dwpcie_atu_write(sc, index, IATU_REGION_CTRL_2, 0);
|
|
}
|
|
|
|
void
|
|
dwpcie_atu_config(struct dwpcie_softc *sc, int index, int type,
|
|
uint64_t cpu_addr, uint64_t pci_addr, uint64_t size)
|
|
{
|
|
uint32_t reg;
|
|
int timo;
|
|
|
|
dwpcie_atu_write(sc, index, IATU_LWR_BASE_ADDR, cpu_addr);
|
|
dwpcie_atu_write(sc, index, IATU_UPPER_BASE_ADDR, cpu_addr >> 32);
|
|
dwpcie_atu_write(sc, index, IATU_LIMIT_ADDR, cpu_addr + size - 1);
|
|
dwpcie_atu_write(sc, index, IATU_LWR_TARGET_ADDR, pci_addr);
|
|
dwpcie_atu_write(sc, index, IATU_UPPER_TARGET_ADDR, pci_addr >> 32);
|
|
dwpcie_atu_write(sc, index, IATU_REGION_CTRL_1, type);
|
|
dwpcie_atu_write(sc, index, IATU_REGION_CTRL_2,
|
|
IATU_REGION_CTRL_2_REGION_EN);
|
|
|
|
for (timo = 5; timo > 0; timo--) {
|
|
reg = dwpcie_atu_read(sc, index, IATU_REGION_CTRL_2);
|
|
if (reg & IATU_REGION_CTRL_2_REGION_EN)
|
|
break;
|
|
delay(9000);
|
|
}
|
|
if (timo == 0)
|
|
printf("%s:%d: timeout\n", __func__, __LINE__);
|
|
}
|
|
|
|
int
|
|
dwpcie_link_up(struct dwpcie_softc *sc)
|
|
{
|
|
uint32_t reg;
|
|
|
|
reg = HREAD4(sc, PCIE_PHY_DEBUG_R1);
|
|
if ((reg & PCIE_PHY_DEBUG_R1_XMLH_LINK_UP) != 0 &&
|
|
(reg & PCIE_PHY_DEBUG_R1_XMLH_LINK_IN_TRAINING) == 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
dwpcie_attach_hook(struct device *parent, struct device *self,
|
|
struct pcibus_attach_args *pba)
|
|
{
|
|
}
|
|
|
|
int
|
|
dwpcie_bus_maxdevs(void *v, int bus)
|
|
{
|
|
struct dwpcie_softc *sc = v;
|
|
|
|
if (bus == sc->sc_bus || bus == sc->sc_bus + 1)
|
|
return 1;
|
|
return 32;
|
|
}
|
|
|
|
int
|
|
dwpcie_find_node(int node, int bus, int device, int function)
|
|
{
|
|
uint32_t reg[5];
|
|
uint32_t phys_hi;
|
|
int child;
|
|
|
|
phys_hi = ((bus << 16) | (device << 11) | (function << 8));
|
|
|
|
for (child = OF_child(node); child; child = OF_peer(child)) {
|
|
if (OF_getpropintarray(child, "reg",
|
|
reg, sizeof(reg)) != sizeof(reg))
|
|
continue;
|
|
|
|
if (reg[0] == phys_hi)
|
|
return child;
|
|
|
|
node = dwpcie_find_node(child, bus, device, function);
|
|
if (node)
|
|
return node;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
pcitag_t
|
|
dwpcie_make_tag(void *v, int bus, int device, int function)
|
|
{
|
|
struct dwpcie_softc *sc = v;
|
|
int node;
|
|
|
|
node = dwpcie_find_node(sc->sc_node, bus, device, function);
|
|
return (((pcitag_t)node << 32) |
|
|
(bus << 24) | (device << 19) | (function << 16));
|
|
}
|
|
|
|
void
|
|
dwpcie_decompose_tag(void *v, pcitag_t tag, int *bp, int *dp, int *fp)
|
|
{
|
|
if (bp != NULL)
|
|
*bp = (tag >> 24) & 0xff;
|
|
if (dp != NULL)
|
|
*dp = (tag >> 19) & 0x1f;
|
|
if (fp != NULL)
|
|
*fp = (tag >> 16) & 0x7;
|
|
}
|
|
|
|
int
|
|
dwpcie_conf_size(void *v, pcitag_t tag)
|
|
{
|
|
return PCIE_CONFIG_SPACE_SIZE;
|
|
}
|
|
|
|
pcireg_t
|
|
dwpcie_conf_read(void *v, pcitag_t tag, int reg)
|
|
{
|
|
struct dwpcie_softc *sc = v;
|
|
int bus, dev, fn;
|
|
uint32_t ret;
|
|
|
|
dwpcie_decompose_tag(sc, tag, &bus, &dev, &fn);
|
|
if (bus == sc->sc_bus) {
|
|
KASSERT(dev == 0);
|
|
tag = dwpcie_make_tag(sc, 0, dev, fn);
|
|
return HREAD4(sc, PCITAG_OFFSET(tag) | reg);
|
|
}
|
|
|
|
if (bus == sc->sc_bus + 1) {
|
|
dwpcie_atu_config(sc, IATU_VIEWPORT_INDEX1,
|
|
IATU_REGION_CTRL_1_TYPE_CFG0,
|
|
sc->sc_conf_base, PCITAG_OFFSET(tag),
|
|
sc->sc_conf_size);
|
|
} else {
|
|
dwpcie_atu_config(sc, IATU_VIEWPORT_INDEX1,
|
|
IATU_REGION_CTRL_1_TYPE_CFG1,
|
|
sc->sc_conf_base, PCITAG_OFFSET(tag),
|
|
sc->sc_conf_size);
|
|
}
|
|
|
|
ret = bus_space_read_4(sc->sc_iot, sc->sc_conf_ioh, reg);
|
|
|
|
if (sc->sc_num_viewport <= 2 && sc->sc_io_size > 0) {
|
|
dwpcie_atu_config(sc, IATU_VIEWPORT_INDEX1,
|
|
IATU_REGION_CTRL_1_TYPE_IO, sc->sc_io_base,
|
|
sc->sc_io_bus_addr, sc->sc_io_size);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
dwpcie_conf_write(void *v, pcitag_t tag, int reg, pcireg_t data)
|
|
{
|
|
struct dwpcie_softc *sc = v;
|
|
int bus, dev, fn;
|
|
|
|
dwpcie_decompose_tag(sc, tag, &bus, &dev, &fn);
|
|
if (bus == sc->sc_bus) {
|
|
KASSERT(dev == 0);
|
|
tag = dwpcie_make_tag(sc, 0, dev, fn);
|
|
HWRITE4(sc, PCITAG_OFFSET(tag) | reg, data);
|
|
return;
|
|
}
|
|
|
|
if (bus == sc->sc_bus + 1) {
|
|
dwpcie_atu_config(sc, IATU_VIEWPORT_INDEX1,
|
|
IATU_REGION_CTRL_1_TYPE_CFG0,
|
|
sc->sc_conf_base, PCITAG_OFFSET(tag),
|
|
sc->sc_conf_size);
|
|
} else {
|
|
dwpcie_atu_config(sc, IATU_VIEWPORT_INDEX1,
|
|
IATU_REGION_CTRL_1_TYPE_CFG1,
|
|
sc->sc_conf_base, PCITAG_OFFSET(tag),
|
|
sc->sc_conf_size);
|
|
}
|
|
|
|
bus_space_write_4(sc->sc_iot, sc->sc_conf_ioh, reg, data);
|
|
|
|
if (sc->sc_num_viewport <= 2 && sc->sc_io_size > 0) {
|
|
dwpcie_atu_config(sc, IATU_VIEWPORT_INDEX1,
|
|
IATU_REGION_CTRL_1_TYPE_IO, sc->sc_io_base,
|
|
sc->sc_io_bus_addr, sc->sc_io_size);
|
|
}
|
|
}
|
|
|
|
int
|
|
dwpcie_probe_device_hook(void *v, struct pci_attach_args *pa)
|
|
{
|
|
struct dwpcie_softc *sc = v;
|
|
uint16_t rid;
|
|
int i;
|
|
|
|
rid = pci_requester_id(pa->pa_pc, pa->pa_tag);
|
|
pa->pa_dmat = iommu_device_map_pci(sc->sc_node, rid, pa->pa_dmat);
|
|
|
|
for (i = 0; i < sc->sc_nranges; i++) {
|
|
iommu_reserve_region_pci(sc->sc_node, rid,
|
|
sc->sc_ranges[i].pci_base, sc->sc_ranges[i].size);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dwpcie_intr_map(struct pci_attach_args *pa, pci_intr_handle_t *ihp)
|
|
{
|
|
int pin = pa->pa_rawintrpin;
|
|
|
|
if (pin == 0 || pin > PCI_INTERRUPT_PIN_MAX)
|
|
return -1;
|
|
|
|
if (pa->pa_tag == 0)
|
|
return -1;
|
|
|
|
ihp->ih_pc = pa->pa_pc;
|
|
ihp->ih_tag = pa->pa_intrtag;
|
|
ihp->ih_intrpin = pa->pa_intrpin;
|
|
ihp->ih_type = PCI_INTX;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *
|
|
dwpcie_intr_string(void *v, pci_intr_handle_t ih)
|
|
{
|
|
switch (ih.ih_type) {
|
|
case PCI_MSI:
|
|
return "msi";
|
|
case PCI_MSIX:
|
|
return "msix";
|
|
}
|
|
|
|
return "intx";
|
|
}
|
|
|
|
struct dwpcie_msi *
|
|
dwpcie_msi_establish(struct dwpcie_softc *sc, pci_intr_handle_t *ihp,
|
|
int level, int (*func)(void *), void *arg, char *name)
|
|
{
|
|
pci_chipset_tag_t pc = ihp->ih_pc;
|
|
pcitag_t tag = ihp->ih_tag;
|
|
struct dwpcie_msi *dm;
|
|
uint64_t msi_mask;
|
|
int vec = ihp->ih_intrpin;
|
|
int base, mme, nvec, off;
|
|
pcireg_t reg;
|
|
|
|
if (ihp->ih_type == PCI_MSI) {
|
|
if (pci_get_capability(pc, tag, PCI_CAP_MSI, &off, ®) == 0)
|
|
panic("%s: no msi capability", __func__);
|
|
|
|
reg = pci_conf_read(ihp->ih_pc, ihp->ih_tag, off);
|
|
mme = ((reg & PCI_MSI_MC_MME_MASK) >> PCI_MSI_MC_MME_SHIFT);
|
|
if (vec >= (1 << mme))
|
|
return NULL;
|
|
if (reg & PCI_MSI_MC_C64)
|
|
base = pci_conf_read(pc, tag, off + PCI_MSI_MD64);
|
|
else
|
|
base = pci_conf_read(pc, tag, off + PCI_MSI_MD32);
|
|
} else {
|
|
mme = 0;
|
|
base = 0;
|
|
}
|
|
|
|
if (vec == 0) {
|
|
/*
|
|
* Pre-allocate all the requested vectors. Remember
|
|
* the number of requested vectors such that we can
|
|
* deallocate them in one go.
|
|
*/
|
|
msi_mask = (1ULL << (1 << mme)) - 1;
|
|
while (vec <= sc->sc_num_msi - (1 << mme)) {
|
|
if ((sc->sc_msi_mask & (msi_mask << vec)) == 0) {
|
|
sc->sc_msi_mask |= (msi_mask << vec);
|
|
break;
|
|
}
|
|
vec += (1 << mme);
|
|
}
|
|
base = vec;
|
|
nvec = (1 << mme);
|
|
} else {
|
|
KASSERT(ihp->ih_type == PCI_MSI);
|
|
vec += base;
|
|
nvec = 0;
|
|
}
|
|
|
|
if (vec >= sc->sc_num_msi)
|
|
return NULL;
|
|
|
|
if (ihp->ih_type == PCI_MSI) {
|
|
if (reg & PCI_MSI_MC_C64)
|
|
pci_conf_write(pc, tag, off + PCI_MSI_MD64, base);
|
|
else
|
|
pci_conf_write(pc, tag, off + PCI_MSI_MD32, base);
|
|
}
|
|
|
|
dm = &sc->sc_msi[vec];
|
|
KASSERT(dm->dm_func == NULL);
|
|
|
|
dm->dm_func = func;
|
|
dm->dm_arg = arg;
|
|
dm->dm_ipl = level & IPL_IRQMASK;
|
|
dm->dm_flags = level & IPL_FLAGMASK;
|
|
dm->dm_vec = vec;
|
|
dm->dm_nvec = nvec;
|
|
dm->dm_name = name;
|
|
if (name != NULL)
|
|
evcount_attach(&dm->dm_count, name, &dm->dm_vec);
|
|
|
|
/* Unmask the MSI. */
|
|
HCLR4(sc, PCIE_MSI_INTR_MASK(vec / 32), (1U << (vec % 32)));
|
|
|
|
return dm;
|
|
}
|
|
|
|
void
|
|
dwpcie_msi_disestablish(struct dwpcie_softc *sc, struct dwpcie_msi *dm)
|
|
{
|
|
uint64_t msi_mask = (1ULL << dm->dm_nvec) - 1;
|
|
|
|
/* Mask the MSI. */
|
|
HSET4(sc, PCIE_MSI_INTR_MASK(dm->dm_vec / 32),
|
|
(1U << (dm->dm_vec % 32)));
|
|
|
|
if (dm->dm_name)
|
|
evcount_detach(&dm->dm_count);
|
|
dm->dm_func = NULL;
|
|
|
|
/*
|
|
* Unallocate all allocated vetcors if this is the first
|
|
* vector for the device.
|
|
*/
|
|
sc->sc_msi_mask &= ~(msi_mask << dm->dm_vec);
|
|
}
|
|
|
|
void *
|
|
dwpcie_intr_establish(void *v, pci_intr_handle_t ih, int level,
|
|
struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
|
|
{
|
|
struct dwpcie_softc *sc = v;
|
|
struct dwpcie_intr_handle *pih;
|
|
void *cookie = NULL;
|
|
|
|
KASSERT(ih.ih_type != PCI_NONE);
|
|
|
|
if (ih.ih_type != PCI_INTX) {
|
|
struct dwpcie_msi *dm = NULL;
|
|
bus_dma_tag_t dmat = ih.ih_dmat;
|
|
bus_dma_segment_t seg;
|
|
bus_dmamap_t map;
|
|
uint64_t addr, data;
|
|
|
|
if (sc->sc_msi_addr) {
|
|
dm = dwpcie_msi_establish(sc, &ih, level, func, arg, name);
|
|
if (dm == NULL)
|
|
return NULL;
|
|
addr = sc->sc_msi_addr;
|
|
data = dm->dm_vec;
|
|
} else {
|
|
/*
|
|
* Assume hardware passes Requester ID as
|
|
* sideband data.
|
|
*/
|
|
addr = ih.ih_intrpin;
|
|
data = pci_requester_id(ih.ih_pc, ih.ih_tag);
|
|
cookie = fdt_intr_establish_msi_cpu(sc->sc_node, &addr,
|
|
&data, level, ci, func, arg, (void *)name);
|
|
if (cookie == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
pih = malloc(sizeof(*pih), M_DEVBUF, M_WAITOK | M_ZERO);
|
|
pih->pih_ih.ih_ic = &dwpcie_ic;
|
|
pih->pih_ih.ih_ih = cookie;
|
|
pih->pih_sc = sc;
|
|
pih->pih_dm = dm;
|
|
|
|
if (sc->sc_msi_addr == 0) {
|
|
if (bus_dmamap_create(dmat, sizeof(uint32_t), 1,
|
|
sizeof(uint32_t), 0, BUS_DMA_WAITOK, &map)) {
|
|
free(pih, M_DEVBUF, sizeof(*pih));
|
|
fdt_intr_disestablish(cookie);
|
|
return NULL;
|
|
}
|
|
|
|
memset(&seg, 0, sizeof(seg));
|
|
seg.ds_addr = addr;
|
|
seg.ds_len = sizeof(uint32_t);
|
|
|
|
if (bus_dmamap_load_raw(dmat, map, &seg, 1,
|
|
sizeof(uint32_t), BUS_DMA_WAITOK)) {
|
|
bus_dmamap_destroy(dmat, map);
|
|
free(pih, M_DEVBUF, sizeof(*pih));
|
|
fdt_intr_disestablish(cookie);
|
|
return NULL;
|
|
}
|
|
|
|
addr = map->dm_segs[0].ds_addr;
|
|
pih->pih_dmat = dmat;
|
|
pih->pih_map = map;
|
|
}
|
|
|
|
if (ih.ih_type == PCI_MSIX) {
|
|
pci_msix_enable(ih.ih_pc, ih.ih_tag,
|
|
&sc->sc_bus_memt, ih.ih_intrpin, addr, data);
|
|
} else
|
|
pci_msi_enable(ih.ih_pc, ih.ih_tag, addr, data);
|
|
} else {
|
|
int bus, dev, fn;
|
|
uint32_t reg[4];
|
|
|
|
dwpcie_decompose_tag(sc, ih.ih_tag, &bus, &dev, &fn);
|
|
|
|
reg[0] = bus << 16 | dev << 11 | fn << 8;
|
|
reg[1] = reg[2] = 0;
|
|
reg[3] = ih.ih_intrpin;
|
|
|
|
cookie = fdt_intr_establish_imap_cpu(sc->sc_node, reg,
|
|
sizeof(reg), level, ci, func, arg, name);
|
|
if (cookie == NULL)
|
|
return NULL;
|
|
|
|
pih = malloc(sizeof(*pih), M_DEVBUF, M_WAITOK | M_ZERO);
|
|
pih->pih_ih.ih_ic = &dwpcie_ic;
|
|
pih->pih_ih.ih_ih = cookie;
|
|
}
|
|
|
|
return pih;
|
|
}
|
|
|
|
void
|
|
dwpcie_intr_disestablish(void *v, void *cookie)
|
|
{
|
|
struct dwpcie_intr_handle *pih = cookie;
|
|
|
|
if (pih->pih_dm)
|
|
dwpcie_msi_disestablish(pih->pih_sc, pih->pih_dm);
|
|
else
|
|
fdt_intr_disestablish(pih->pih_ih.ih_ih);
|
|
|
|
if (pih->pih_dmat) {
|
|
bus_dmamap_unload(pih->pih_dmat, pih->pih_map);
|
|
bus_dmamap_destroy(pih->pih_dmat, pih->pih_map);
|
|
}
|
|
|
|
free(pih, M_DEVBUF, sizeof(*pih));
|
|
}
|
|
|
|
int
|
|
dwpcie_bs_iomap(bus_space_tag_t t, bus_addr_t addr, bus_size_t size,
|
|
int flags, bus_space_handle_t *bshp)
|
|
{
|
|
struct dwpcie_softc *sc = t->bus_private;
|
|
int i;
|
|
|
|
for (i = 0; i < sc->sc_nranges; i++) {
|
|
uint64_t pci_start = sc->sc_ranges[i].pci_base;
|
|
uint64_t pci_end = pci_start + sc->sc_ranges[i].size;
|
|
uint64_t phys_start = sc->sc_ranges[i].phys_base;
|
|
|
|
if ((sc->sc_ranges[i].flags & 0x03000000) == 0x01000000 &&
|
|
addr >= pci_start && addr + size <= pci_end) {
|
|
return bus_space_map(sc->sc_iot,
|
|
addr - pci_start + phys_start, size, flags, bshp);
|
|
}
|
|
}
|
|
|
|
return ENXIO;
|
|
}
|
|
|
|
int
|
|
dwpcie_bs_memmap(bus_space_tag_t t, bus_addr_t addr, bus_size_t size,
|
|
int flags, bus_space_handle_t *bshp)
|
|
{
|
|
struct dwpcie_softc *sc = t->bus_private;
|
|
int i;
|
|
|
|
for (i = 0; i < sc->sc_nranges; i++) {
|
|
uint64_t pci_start = sc->sc_ranges[i].pci_base;
|
|
uint64_t pci_end = pci_start + sc->sc_ranges[i].size;
|
|
uint64_t phys_start = sc->sc_ranges[i].phys_base;
|
|
|
|
if ((sc->sc_ranges[i].flags & 0x02000000) == 0x02000000 &&
|
|
addr >= pci_start && addr + size <= pci_end) {
|
|
return bus_space_map(sc->sc_iot,
|
|
addr - pci_start + phys_start, size, flags, bshp);
|
|
}
|
|
}
|
|
|
|
return ENXIO;
|
|
}
|