diff options
Diffstat (limited to 'drivers')
29 files changed, 6451 insertions, 185 deletions
diff --git a/drivers/clk/mediatek/Makefile b/drivers/clk/mediatek/Makefile index 0632dc87b6..a47a5bdbc2 100644 --- a/drivers/clk/mediatek/Makefile +++ b/drivers/clk/mediatek/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_ARCH_MEDIATEK) += clk-mtk.o # SoC Drivers obj-$(CONFIG_TARGET_MT7623) += clk-mt7623.o obj-$(CONFIG_TARGET_MT7629) += clk-mt7629.o +obj-$(CONFIG_TARGET_MT8516) += clk-mt8516.o diff --git a/drivers/clk/mediatek/clk-mt8516.c b/drivers/clk/mediatek/clk-mt8516.c new file mode 100644 index 0000000000..071bf69b19 --- /dev/null +++ b/drivers/clk/mediatek/clk-mt8516.c @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek clock driver for MT8516 SoC + * + * Copyright (C) 2018 BayLibre, SAS + * Author: Fabien Parent <fparent@baylibre.com> + */ + +#include <common.h> +#include <dm.h> +#include <asm/io.h> +#include <dt-bindings/clock/mt8516-clk.h> + +#include "clk-mtk.h" + +#define MT8516_PLL_FMAX (1502UL * MHZ) +#define MT8516_CON0_RST_BAR BIT(27) + +/* apmixedsys */ +#define PLL(_id, _reg, _pwr_reg, _en_mask, _flags, _pcwbits, _pd_reg, \ + _pd_shift, _pcw_reg, _pcw_shift) { \ + .id = _id, \ + .reg = _reg, \ + .pwr_reg = _pwr_reg, \ + .en_mask = _en_mask, \ + .rst_bar_mask = MT8516_CON0_RST_BAR, \ + .fmax = MT8516_PLL_FMAX, \ + .flags = _flags, \ + .pcwbits = _pcwbits, \ + .pd_reg = _pd_reg, \ + .pd_shift = _pd_shift, \ + .pcw_reg = _pcw_reg, \ + .pcw_shift = _pcw_shift, \ + } + +static const struct mtk_pll_data apmixed_plls[] = { + PLL(CLK_APMIXED_ARMPLL, 0x0100, 0x0110, 0x00000001, 0, + 21, 0x0104, 24, 0x0104, 0), + PLL(CLK_APMIXED_MAINPLL, 0x0120, 0x0130, 0x00000001, + HAVE_RST_BAR, 21, 0x0124, 24, 0x0124, 0), + PLL(CLK_APMIXED_UNIVPLL, 0x0140, 0x0150, 0x30000001, + HAVE_RST_BAR, 7, 0x0144, 24, 0x0144, 0), + PLL(CLK_APMIXED_MMPLL, 0x0160, 0x0170, 0x00000001, 0, + 21, 0x0164, 24, 0x0164, 0), + PLL(CLK_APMIXED_APLL1, 0x0180, 0x0190, 0x00000001, 0, + 31, 0x0180, 1, 0x0184, 0), + PLL(CLK_APMIXED_APLL2, 0x01A0, 0x01B0, 0x00000001, 0, + 31, 0x01A0, 1, 0x01A4, 0), +}; + +/* topckgen */ +#define FACTOR0(_id, _parent, _mult, _div) \ + FACTOR(_id, _parent, _mult, _div, CLK_PARENT_APMIXED) + +#define FACTOR1(_id, _parent, _mult, _div) \ + FACTOR(_id, _parent, _mult, _div, CLK_PARENT_TOPCKGEN) + +#define FACTOR2(_id, _parent, _mult, _div) \ + FACTOR(_id, _parent, _mult, _div, 0) + +static const struct mtk_fixed_clk top_fixed_clks[] = { + FIXED_CLK(CLK_TOP_CLK_NULL, CLK_XTAL, 26000000), + FIXED_CLK(CLK_TOP_I2S_INFRA_BCK, CLK_TOP_CLK_NULL, 26000000), + FIXED_CLK(CLK_TOP_MEMPLL, CLK_TOP_CLK26M, 800000000), +}; + +static const struct mtk_fixed_factor top_fixed_divs[] = { + FACTOR1(CLK_TOP_DMPLL, CLK_TOP_MEMPLL, 1, 1), + FACTOR0(CLK_TOP_MAINPLL_D2, CLK_APMIXED_MAINPLL, 1, 2), + FACTOR0(CLK_TOP_MAINPLL_D4, CLK_APMIXED_MAINPLL, 1, 4), + FACTOR0(CLK_TOP_MAINPLL_D8, CLK_APMIXED_MAINPLL, 1, 8), + FACTOR0(CLK_TOP_MAINPLL_D16, CLK_APMIXED_MAINPLL, 1, 16), + FACTOR0(CLK_TOP_MAINPLL_D11, CLK_APMIXED_MAINPLL, 1, 11), + FACTOR0(CLK_TOP_MAINPLL_D22, CLK_APMIXED_MAINPLL, 1, 22), + FACTOR0(CLK_TOP_MAINPLL_D3, CLK_APMIXED_MAINPLL, 1, 3), + FACTOR0(CLK_TOP_MAINPLL_D6, CLK_APMIXED_MAINPLL, 1, 6), + FACTOR0(CLK_TOP_MAINPLL_D12, CLK_APMIXED_MAINPLL, 1, 12), + FACTOR0(CLK_TOP_MAINPLL_D5, CLK_APMIXED_MAINPLL, 1, 5), + FACTOR0(CLK_TOP_MAINPLL_D10, CLK_APMIXED_MAINPLL, 1, 10), + FACTOR0(CLK_TOP_MAINPLL_D20, CLK_APMIXED_MAINPLL, 1, 20), + FACTOR0(CLK_TOP_MAINPLL_D40, CLK_APMIXED_MAINPLL, 1, 40), + FACTOR0(CLK_TOP_MAINPLL_D7, CLK_APMIXED_MAINPLL, 1, 7), + FACTOR0(CLK_TOP_MAINPLL_D14, CLK_APMIXED_MAINPLL, 1, 14), + FACTOR0(CLK_TOP_UNIVPLL_D2, CLK_APMIXED_UNIVPLL, 1, 2), + FACTOR0(CLK_TOP_UNIVPLL_D4, CLK_APMIXED_UNIVPLL, 1, 4), + FACTOR0(CLK_TOP_UNIVPLL_D8, CLK_APMIXED_UNIVPLL, 1, 8), + FACTOR0(CLK_TOP_UNIVPLL_D16, CLK_APMIXED_UNIVPLL, 1, 16), + FACTOR0(CLK_TOP_UNIVPLL_D3, CLK_APMIXED_UNIVPLL, 1, 3), + FACTOR0(CLK_TOP_UNIVPLL_D6, CLK_APMIXED_UNIVPLL, 1, 6), + FACTOR0(CLK_TOP_UNIVPLL_D12, CLK_APMIXED_UNIVPLL, 1, 12), + FACTOR0(CLK_TOP_UNIVPLL_D24, CLK_APMIXED_UNIVPLL, 1, 24), + FACTOR0(CLK_TOP_UNIVPLL_D5, CLK_APMIXED_UNIVPLL, 1, 5), + FACTOR0(CLK_TOP_UNIVPLL_D20, CLK_APMIXED_UNIVPLL, 1, 20), + FACTOR0(CLK_TOP_MMPLL380M, CLK_APMIXED_MMPLL, 1, 1), + FACTOR0(CLK_TOP_MMPLL_D2, CLK_APMIXED_MMPLL, 1, 2), + FACTOR0(CLK_TOP_MMPLL_200M, CLK_APMIXED_MMPLL, 1, 3), + FACTOR0(CLK_TOP_USB_PHY48M, CLK_APMIXED_UNIVPLL, 1, 26), + FACTOR0(CLK_TOP_APLL1, CLK_APMIXED_APLL1, 1, 1), + FACTOR1(CLK_TOP_APLL1_D2, CLK_TOP_APLL1, 1, 2), + FACTOR1(CLK_TOP_APLL1_D4, CLK_TOP_RG_APLL1_D2_EN, 1, 2), + FACTOR1(CLK_TOP_APLL1_D8, CLK_TOP_RG_APLL1_D4_EN, 1, 2), + FACTOR0(CLK_TOP_APLL2, CLK_APMIXED_APLL2, 1, 1), + FACTOR1(CLK_TOP_APLL2_D2, CLK_TOP_APLL2, 1, 2), + FACTOR1(CLK_TOP_APLL2_D4, CLK_TOP_RG_APLL2_D2_EN, 1, 2), + FACTOR1(CLK_TOP_APLL2_D8, CLK_TOP_RG_APLL2_D4_EN, 1, 2), + FACTOR2(CLK_TOP_CLK26M, CLK_XTAL, 1, 1), + FACTOR2(CLK_TOP_CLK26M_D2, CLK_XTAL, 1, 2), + FACTOR1(CLK_TOP_AHB_INFRA_D2, CLK_TOP_AHB_INFRA_SEL, 1, 2), + FACTOR1(CLK_TOP_NFI1X, CLK_TOP_NFI2X_PAD_SEL, 1, 2), + FACTOR1(CLK_TOP_ETH_D2, CLK_TOP_ETH_SEL, 1, 2), +}; + +static const int uart0_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D24, +}; + +static const int gfmux_emi1x_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_DMPLL, +}; + +static const int emi_ddrphy_parents[] = { + CLK_TOP_GFMUX_EMI1X_SEL, + CLK_TOP_GFMUX_EMI1X_SEL, +}; + +static const int ahb_infra_parents[] = { + CLK_TOP_CLK_NULL, + CLK_TOP_CLK26M, + CLK_TOP_MAINPLL_D11, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D12, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D10, +}; + +static const int csw_mux_mfg_parents[] = { + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_UNIVPLL_D3, + CLK_TOP_UNIVPLL_D2, + CLK_TOP_CLK26M, + CLK_TOP_MAINPLL_D4, + CLK_TOP_UNIVPLL_D24, + CLK_TOP_MMPLL380M, +}; + +static const int msdc0_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D6, + CLK_TOP_MAINPLL_D8, + CLK_TOP_UNIVPLL_D8, + CLK_TOP_MAINPLL_D16, + CLK_TOP_MMPLL_200M, + CLK_TOP_MAINPLL_D12, + CLK_TOP_MMPLL_D2, +}; + +static const int pwm_mm_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D12, +}; + +static const int uart1_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D24, +}; + +static const int msdc1_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D6, + CLK_TOP_MAINPLL_D8, + CLK_TOP_UNIVPLL_D8, + CLK_TOP_MAINPLL_D16, + CLK_TOP_MMPLL_200M, + CLK_TOP_MAINPLL_D12, + CLK_TOP_MMPLL_D2, +}; + +static const int spm_52m_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D24, +}; + +static const int pmicspi_parents[] = { + CLK_TOP_UNIVPLL_D20, + CLK_TOP_USB_PHY48M, + CLK_TOP_UNIVPLL_D16, + CLK_TOP_CLK26M, +}; + +static const int qaxi_aud26m_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_AHB_INFRA_SEL, +}; + +static const int aud_intbus_parents[] = { + CLK_TOP_CLK_NULL, + CLK_TOP_CLK26M, + CLK_TOP_MAINPLL_D22, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D11, +}; + +static const int nfi2x_pad_parents[] = { + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK26M, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D12, + CLK_TOP_MAINPLL_D8, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D6, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D4, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D10, + CLK_TOP_MAINPLL_D7, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D5 +}; + +static const int nfi1x_pad_parents[] = { + CLK_TOP_AHB_INFRA_SEL, + CLK_TOP_NFI1X, +}; + +static const int mfg_mm_parents[] = { + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CSW_MUX_MFG_SEL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D3, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D5, + CLK_TOP_MAINPLL_D7, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D14 +}; + +static const int ddrphycfg_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_MAINPLL_D16 +}; + +static const int usb_78m_parents[] = { + CLK_TOP_CLK_NULL, + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D16, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D20, +}; + +static const int spinor_parents[] = { + CLK_TOP_CLK26M_D2, + CLK_TOP_CLK26M, + CLK_TOP_MAINPLL_D40, + CLK_TOP_UNIVPLL_D24, + CLK_TOP_UNIVPLL_D20, + CLK_TOP_MAINPLL_D20, + CLK_TOP_MAINPLL_D16, + CLK_TOP_UNIVPLL_D12 +}; + +static const int msdc2_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D6, + CLK_TOP_MAINPLL_D8, + CLK_TOP_UNIVPLL_D8, + CLK_TOP_MAINPLL_D16, + CLK_TOP_MMPLL_200M, + CLK_TOP_MAINPLL_D12, + CLK_TOP_MMPLL_D2 +}; + +static const int eth_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_MAINPLL_D40, + CLK_TOP_UNIVPLL_D24, + CLK_TOP_UNIVPLL_D20, + CLK_TOP_MAINPLL_D20 +}; + +static const int axi_mfg_in_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_MAINPLL_D11, + CLK_TOP_UNIVPLL_D24, + CLK_TOP_MMPLL380M, +}; + +static const int slow_mfg_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D12, + CLK_TOP_UNIVPLL_D24 +}; + +static const int aud1_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_APLL1 +}; + +static const int aud2_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_APLL2 +}; + +static const int aud_engen1_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_RG_APLL1_D2_EN, + CLK_TOP_RG_APLL1_D4_EN, + CLK_TOP_RG_APLL1_D8_EN +}; + +static const int aud_engen2_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_RG_APLL2_D2_EN, + CLK_TOP_RG_APLL2_D4_EN, + CLK_TOP_RG_APLL2_D8_EN +}; + +static const int i2c_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D20, + CLK_TOP_UNIVPLL_D16, + CLK_TOP_UNIVPLL_D12 +}; + +static const int aud_i2s0_m_parents[] = { + CLK_TOP_RG_AUD1, + CLK_TOP_RG_AUD2 +}; + +static const int pwm_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D12 +}; + +static const int spi_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D12, + CLK_TOP_UNIVPLL_D8, + CLK_TOP_UNIVPLL_D6 +}; + +static const int aud_spdifin_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D2 +}; + +static const int uart2_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_UNIVPLL_D24 +}; + +static const int bsi_parents[] = { + CLK_TOP_CLK26M, + CLK_TOP_MAINPLL_D10, + CLK_TOP_MAINPLL_D12, + CLK_TOP_MAINPLL_D20 +}; + +static const int dbg_atclk_parents[] = { + CLK_TOP_CLK_NULL, + CLK_TOP_CLK26M, + CLK_TOP_MAINPLL_D5, + CLK_TOP_CLK_NULL, + CLK_TOP_UNIVPLL_D5 +}; + +static const int csw_nfiecc_parents[] = { + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D7, + CLK_TOP_MAINPLL_D6, + CLK_TOP_CLK_NULL, + CLK_TOP_MAINPLL_D5 +}; + +static const int nfiecc_parents[] = { + CLK_TOP_CLK_NULL, + CLK_TOP_NFI2X_PAD_SEL, + CLK_TOP_MAINPLL_D4, + CLK_TOP_CLK_NULL, + CLK_TOP_CSW_NFIECC_SEL, +}; + +static const struct mtk_composite top_muxes[] = { + /* CLK_MUX_SEL0 */ + MUX(CLK_TOP_UART0_SEL, uart0_parents, 0x000, 0, 1), + MUX(CLK_TOP_GFMUX_EMI1X_SEL, gfmux_emi1x_parents, 0x000, 1, 1), + MUX(CLK_TOP_EMI_DDRPHY_SEL, emi_ddrphy_parents, 0x000, 2, 1), + MUX(CLK_TOP_AHB_INFRA_SEL, ahb_infra_parents, 0x000, 4, 4), + MUX(CLK_TOP_CSW_MUX_MFG_SEL, csw_mux_mfg_parents, 0x000, 8, 3), + MUX(CLK_TOP_MSDC0_SEL, msdc0_parents, 0x000, 11, 3), + MUX(CLK_TOP_PWM_MM_SEL, pwm_mm_parents, 0x000, 18, 1), + MUX(CLK_TOP_UART1_SEL, uart1_parents, 0x000, 19, 1), + MUX(CLK_TOP_MSDC1_SEL, msdc1_parents, 0x000, 20, 3), + MUX(CLK_TOP_SPM_52M_SEL, spm_52m_parents, 0x000, 23, 1), + MUX(CLK_TOP_PMICSPI_SEL, pmicspi_parents, 0x000, 24, 2), + MUX(CLK_TOP_QAXI_AUD26M_SEL, qaxi_aud26m_parents, 0x000, 26, 1), + MUX(CLK_TOP_AUD_INTBUS_SEL, aud_intbus_parents, 0x000, 27, 3), + /* CLK_MUX_SEL1 */ + MUX(CLK_TOP_NFI2X_PAD_SEL, nfi2x_pad_parents, 0x004, 0, 7), + MUX(CLK_TOP_NFI1X_PAD_SEL, nfi1x_pad_parents, 0x004, 7, 1), + MUX(CLK_TOP_MFG_MM_SEL, mfg_mm_parents, 0x004, 8, 6), + MUX(CLK_TOP_DDRPHYCFG_SEL, ddrphycfg_parents, 0x004, 15, 1), + MUX(CLK_TOP_USB_78M_SEL, usb_78m_parents, 0x004, 20, 3), + /* CLK_MUX_SEL8 */ + MUX(CLK_TOP_SPINOR_SEL, spinor_parents, 0x040, 0, 3), + MUX(CLK_TOP_MSDC2_SEL, msdc2_parents, 0x040, 3, 3), + MUX(CLK_TOP_ETH_SEL, eth_parents, 0x040, 6, 3), + MUX(CLK_TOP_AXI_MFG_IN_SEL, axi_mfg_in_parents, 0x040, 18, 2), + MUX(CLK_TOP_SLOW_MFG_SEL, slow_mfg_parents, 0x040, 20, 2), + MUX(CLK_TOP_AUD1_SEL, aud1_parents, 0x040, 22, 1), + MUX(CLK_TOP_AUD2_SEL, aud2_parents, 0x040, 23, 1), + MUX(CLK_TOP_AUD_ENGEN1_SEL, aud_engen1_parents, 0x040, 24, 2), + MUX(CLK_TOP_AUD_ENGEN2_SEL, aud_engen2_parents, 0x040, 26, 2), + MUX(CLK_TOP_I2C_SEL, i2c_parents, 0x040, 28, 2), + /* CLK_MUX_SEL9 */ + MUX(CLK_TOP_AUD_I2S0_M_SEL, aud_i2s0_m_parents, 0x044, 12, 1), + MUX(CLK_TOP_AUD_I2S1_M_SEL, aud_i2s0_m_parents, 0x044, 13, 1), + MUX(CLK_TOP_AUD_I2S2_M_SEL, aud_i2s0_m_parents, 0x044, 14, 1), + MUX(CLK_TOP_AUD_I2S3_M_SEL, aud_i2s0_m_parents, 0x044, 15, 1), + MUX(CLK_TOP_AUD_I2S4_M_SEL, aud_i2s0_m_parents, 0x044, 16, 1), + MUX(CLK_TOP_AUD_I2S5_M_SEL, aud_i2s0_m_parents, 0x044, 17, 1), + MUX(CLK_TOP_AUD_SPDIF_B_SEL, aud_i2s0_m_parents, 0x044, 18, 1), + /* CLK_MUX_SEL13 */ + MUX(CLK_TOP_PWM_SEL, pwm_parents, 0x07c, 0, 1), + MUX(CLK_TOP_SPI_SEL, spi_parents, 0x07c, 1, 2), + MUX(CLK_TOP_AUD_SPDIFIN_SEL, aud_spdifin_parents, 0x07c, 3, 1), + MUX(CLK_TOP_UART2_SEL, uart2_parents, 0x07c, 4, 1), + MUX(CLK_TOP_BSI_SEL, bsi_parents, 0x07c, 5, 2), + MUX(CLK_TOP_DBG_ATCLK_SEL, dbg_atclk_parents, 0x07c, 7, 3), + MUX(CLK_TOP_CSW_NFIECC_SEL, csw_nfiecc_parents, 0x07c, 10, 3), + MUX(CLK_TOP_NFIECC_SEL, nfiecc_parents, 0x07c, 13, 3), +}; + +static const struct mtk_gate_regs top0_cg_regs = { + .set_ofs = 0x50, + .clr_ofs = 0x80, + .sta_ofs = 0x20, +}; + +static const struct mtk_gate_regs top1_cg_regs = { + .set_ofs = 0x54, + .clr_ofs = 0x84, + .sta_ofs = 0x24, +}; + +static const struct mtk_gate_regs top2_cg_regs = { + .set_ofs = 0x6c, + .clr_ofs = 0x9c, + .sta_ofs = 0x3c, +}; + +static const struct mtk_gate_regs top3_cg_regs = { + .set_ofs = 0xa0, + .clr_ofs = 0xb0, + .sta_ofs = 0x70, +}; + +static const struct mtk_gate_regs top4_cg_regs = { + .set_ofs = 0xa4, + .clr_ofs = 0xb4, + .sta_ofs = 0x74, +}; + +static const struct mtk_gate_regs top5_cg_regs = { + .set_ofs = 0x44, + .clr_ofs = 0x44, + .sta_ofs = 0x44, +}; + +#define GATE_TOP0(_id, _parent, _shift) { \ + .id = _id, \ + .parent = _parent, \ + .regs = &top0_cg_regs, \ + .shift = _shift, \ + .flags = CLK_GATE_SETCLR | CLK_PARENT_TOPCKGEN, \ + } + +#define GATE_TOP1(_id, _parent, _shift) { \ + .id = _id, \ + .parent = _parent, \ + .regs = &top1_cg_regs, \ + .shift = _shift, \ + .flags = CLK_GATE_SETCLR | CLK_PARENT_TOPCKGEN, \ + } + +#define GATE_TOP2(_id, _parent, _shift) { \ + .id = _id, \ + .parent = _parent, \ + .regs = &top2_cg_regs, \ + .shift = _shift, \ + .flags = CLK_GATE_SETCLR | CLK_PARENT_TOPCKGEN, \ + } + +#define GATE_TOP2_I(_id, _parent, _shift) { \ + .id = _id, \ + .parent = _parent, \ + .regs = &top2_cg_regs, \ + .shift = _shift, \ + .flags = CLK_GATE_SETCLR_INV | CLK_PARENT_TOPCKGEN, \ + } + +#define GATE_TOP3(_id, _parent, _shift) { \ + .id = _id, \ + .parent = _parent, \ + .regs = &top3_cg_regs, \ + .shift = _shift, \ + .flags = CLK_GATE_SETCLR | CLK_PARENT_TOPCKGEN, \ + } + +#define GATE_TOP4_I(_id, _parent, _shift) { \ + .id = _id, \ + .parent = _parent, \ + .regs = &top4_cg_regs, \ + .shift = _shift, \ + .flags = CLK_GATE_SETCLR_INV | CLK_PARENT_TOPCKGEN, \ + } + +#define GATE_TOP5(_id, _parent, _shift) { \ + .id = _id, \ + .parent = _parent, \ + .regs = &top5_cg_regs, \ + .shift = _shift, \ + .flags = CLK_GATE_NO_SETCLR | CLK_PARENT_TOPCKGEN, \ + } + +static const struct mtk_gate top_clks[] = { + /* TOP0 */ + GATE_TOP0(CLK_TOP_PWM_MM, CLK_TOP_PWM_MM_SEL, 0), + GATE_TOP0(CLK_TOP_MFG_MM, CLK_TOP_MFG_MM_SEL, 2), + GATE_TOP0(CLK_TOP_SPM_52M, CLK_TOP_SPM_52M_SEL, 3), + /* TOP1 */ + GATE_TOP1(CLK_TOP_THEM, CLK_TOP_AHB_INFRA_SEL, 1), + GATE_TOP1(CLK_TOP_APDMA, CLK_TOP_AHB_INFRA_SEL, 2), + GATE_TOP1(CLK_TOP_I2C0, CLK_IFR_I2C0_SEL, 3), + GATE_TOP1(CLK_TOP_I2C1, CLK_IFR_I2C1_SEL, 4), + GATE_TOP1(CLK_TOP_AUXADC1, CLK_TOP_AHB_INFRA_SEL, 5), + GATE_TOP1(CLK_TOP_NFI, CLK_TOP_NFI1X_PAD_SEL, 6), + GATE_TOP1(CLK_TOP_NFIECC, CLK_TOP_RG_NFIECC, 7), + GATE_TOP1(CLK_TOP_DEBUGSYS, CLK_TOP_RG_DBG_ATCLK, 8), + GATE_TOP1(CLK_TOP_PWM, CLK_TOP_AHB_INFRA_SEL, 9), + GATE_TOP1(CLK_TOP_UART0, CLK_TOP_UART0_SEL, 10), + GATE_TOP1(CLK_TOP_UART1, CLK_TOP_UART1_SEL, 11), + GATE_TOP1(CLK_TOP_BTIF, CLK_TOP_AHB_INFRA_SEL, 12), + GATE_TOP1(CLK_TOP_USB, CLK_TOP_USB_78M, 13), + GATE_TOP1(CLK_TOP_FLASHIF_26M, CLK_TOP_CLK26M, 14), + GATE_TOP1(CLK_TOP_AUXADC2, CLK_TOP_AHB_INFRA_SEL, 15), + GATE_TOP1(CLK_TOP_I2C2, CLK_IFR_I2C2_SEL, 16), + GATE_TOP1(CLK_TOP_MSDC0, CLK_TOP_MSDC0_SEL, 17), + GATE_TOP1(CLK_TOP_MSDC1, CLK_TOP_MSDC1_SEL, 18), + GATE_TOP1(CLK_TOP_NFI2X, CLK_TOP_NFI2X_PAD_SEL, 19), + GATE_TOP1(CLK_TOP_PMICWRAP_AP, CLK_TOP_CLK26M, 20), + GATE_TOP1(CLK_TOP_SEJ, CLK_TOP_AHB_INFRA_SEL, 21), + GATE_TOP1(CLK_TOP_MEMSLP_DLYER, CLK_TOP_CLK26M, 22), + GATE_TOP1(CLK_TOP_SPI, CLK_TOP_SPI_SEL, 23), + GATE_TOP1(CLK_TOP_APXGPT, CLK_TOP_CLK26M, 24), + GATE_TOP1(CLK_TOP_AUDIO, CLK_TOP_CLK26M, 25), + GATE_TOP1(CLK_TOP_PMICWRAP_MD, CLK_TOP_CLK26M, 27), + GATE_TOP1(CLK_TOP_PMICWRAP_CONN, CLK_TOP_CLK26M, 28), + GATE_TOP1(CLK_TOP_PMICWRAP_26M, CLK_TOP_CLK26M, 29), + GATE_TOP1(CLK_TOP_AUX_ADC, CLK_TOP_CLK26M, 30), + GATE_TOP1(CLK_TOP_AUX_TP, CLK_TOP_CLK26M, 31), + /* TOP2 */ + GATE_TOP2(CLK_TOP_MSDC2, CLK_TOP_AHB_INFRA_SEL, 0), + GATE_TOP2(CLK_TOP_RBIST, CLK_TOP_UNIVPLL_D12, 1), + GATE_TOP2(CLK_TOP_NFI_BUS, CLK_TOP_AHB_INFRA_SEL, 2), + GATE_TOP2(CLK_TOP_GCE, CLK_TOP_AHB_INFRA_SEL, 4), + GATE_TOP2(CLK_TOP_TRNG, CLK_TOP_AHB_INFRA_SEL, 5), + GATE_TOP2(CLK_TOP_SEJ_13M, CLK_TOP_CLK26M, 6), + GATE_TOP2(CLK_TOP_AES, CLK_TOP_AHB_INFRA_SEL, 7), + GATE_TOP2(CLK_TOP_PWM_B, CLK_TOP_RG_PWM_INFRA, 8), + GATE_TOP2(CLK_TOP_PWM1_FB, CLK_TOP_RG_PWM_INFRA, 9), + GATE_TOP2(CLK_TOP_PWM2_FB, CLK_TOP_RG_PWM_INFRA, 10), + GATE_TOP2(CLK_TOP_PWM3_FB, CLK_TOP_RG_PWM_INFRA, 11), + GATE_TOP2(CLK_TOP_PWM4_FB, CLK_TOP_RG_PWM_INFRA, 12), + GATE_TOP2(CLK_TOP_PWM5_FB, CLK_TOP_RG_PWM_INFRA, 13), + GATE_TOP2(CLK_TOP_USB_1P, CLK_TOP_USB_78M, 14), + GATE_TOP2(CLK_TOP_FLASHIF_FREERUN, CLK_TOP_AHB_INFRA_SEL, 15), + GATE_TOP2(CLK_TOP_66M_ETH, CLK_TOP_AHB_INFRA_D2, 19), + GATE_TOP2(CLK_TOP_133M_ETH, CLK_TOP_AHB_INFRA_SEL, 20), + GATE_TOP2(CLK_TOP_FETH_25M, CLK_IFR_ETH_25M_SEL, 21), + GATE_TOP2(CLK_TOP_FETH_50M, CLK_TOP_RG_ETH, 22), + GATE_TOP2(CLK_TOP_FLASHIF_AXI, CLK_TOP_AHB_INFRA_SEL, 23), + GATE_TOP2(CLK_TOP_USBIF, CLK_TOP_AHB_INFRA_SEL, 24), + GATE_TOP2(CLK_TOP_UART2, CLK_TOP_RG_UART2, 25), + GATE_TOP2(CLK_TOP_BSI, CLK_TOP_AHB_INFRA_SEL, 26), + GATE_TOP2_I(CLK_TOP_MSDC0_INFRA, CLK_TOP_MSDC0, 28), + GATE_TOP2_I(CLK_TOP_MSDC1_INFRA, CLK_TOP_MSDC1, 29), + GATE_TOP2_I(CLK_TOP_MSDC2_INFRA, CLK_TOP_RG_MSDC2, 30), + GATE_TOP2(CLK_TOP_USB_78M, CLK_TOP_USB_78M_SEL, 31), + /* TOP3 */ + GATE_TOP3(CLK_TOP_RG_SPINOR, CLK_TOP_SPINOR_SEL, 0), + GATE_TOP3(CLK_TOP_RG_MSDC2, CLK_TOP_MSDC2_SEL, 1), + GATE_TOP3(CLK_TOP_RG_ETH, CLK_TOP_ETH_SEL, 2), + GATE_TOP3(CLK_TOP_RG_AXI_MFG, CLK_TOP_AXI_MFG_IN_SEL, 6), + GATE_TOP3(CLK_TOP_RG_SLOW_MFG, CLK_TOP_SLOW_MFG_SEL, 7), + GATE_TOP3(CLK_TOP_RG_AUD1, CLK_TOP_AUD1_SEL, 8), + GATE_TOP3(CLK_TOP_RG_AUD2, CLK_TOP_AUD2_SEL, 9), + GATE_TOP3(CLK_TOP_RG_AUD_ENGEN1, CLK_TOP_AUD_ENGEN1_SEL, 10), + GATE_TOP3(CLK_TOP_RG_AUD_ENGEN2, CLK_TOP_AUD_ENGEN2_SEL, 11), + GATE_TOP3(CLK_TOP_RG_I2C, CLK_TOP_I2C_SEL, 12), + GATE_TOP3(CLK_TOP_RG_PWM_INFRA, CLK_TOP_PWM_SEL, 13), + GATE_TOP3(CLK_TOP_RG_AUD_SPDIF_IN, CLK_TOP_AUD_SPDIFIN_SEL, 14), + GATE_TOP3(CLK_TOP_RG_UART2, CLK_TOP_UART2_SEL, 15), + GATE_TOP3(CLK_TOP_RG_BSI, CLK_TOP_BSI_SEL, 16), + GATE_TOP3(CLK_TOP_RG_DBG_ATCLK, CLK_TOP_DBG_ATCLK_SEL, 17), + GATE_TOP3(CLK_TOP_RG_NFIECC, CLK_TOP_NFIECC_SEL, 18), + /* TOP4 */ + GATE_TOP4_I(CLK_TOP_RG_APLL1_D2_EN, CLK_TOP_APLL1_D2, 8), + GATE_TOP4_I(CLK_TOP_RG_APLL1_D4_EN, CLK_TOP_APLL1_D4, 9), + GATE_TOP4_I(CLK_TOP_RG_APLL1_D8_EN, CLK_TOP_APLL1_D8, 10), + GATE_TOP4_I(CLK_TOP_RG_APLL2_D2_EN, CLK_TOP_APLL2_D2, 11), + GATE_TOP4_I(CLK_TOP_RG_APLL2_D4_EN, CLK_TOP_APLL2_D4, 12), + GATE_TOP4_I(CLK_TOP_RG_APLL2_D8_EN, CLK_TOP_APLL2_D8, 13), + /* TOP5 */ + GATE_TOP5(CLK_TOP_APLL12_DIV0, CLK_TOP_APLL12_CK_DIV0, 0), + GATE_TOP5(CLK_TOP_APLL12_DIV1, CLK_TOP_APLL12_CK_DIV1, 1), + GATE_TOP5(CLK_TOP_APLL12_DIV2, CLK_TOP_APLL12_CK_DIV2, 2), + GATE_TOP5(CLK_TOP_APLL12_DIV3, CLK_TOP_APLL12_CK_DIV3, 3), + GATE_TOP5(CLK_TOP_APLL12_DIV4, CLK_TOP_APLL12_CK_DIV4, 4), + GATE_TOP5(CLK_TOP_APLL12_DIV4B, CLK_TOP_APLL12_CK_DIV4B, 5), + GATE_TOP5(CLK_TOP_APLL12_DIV5, CLK_TOP_APLL12_CK_DIV5, 6), + GATE_TOP5(CLK_TOP_APLL12_DIV5B, CLK_TOP_APLL12_CK_DIV5B, 7), + GATE_TOP5(CLK_TOP_APLL12_DIV6, CLK_TOP_APLL12_CK_DIV6, 8), +}; + +static const struct mtk_clk_tree mt8516_clk_tree = { + .xtal_rate = 26 * MHZ, + .xtal2_rate = 26 * MHZ, + .fdivs_offs = CLK_TOP_DMPLL, + .muxes_offs = CLK_TOP_UART0_SEL, + .plls = apmixed_plls, + .fclks = top_fixed_clks, + .fdivs = top_fixed_divs, + .muxes = top_muxes, +}; + +static int mt8516_apmixedsys_probe(struct udevice *dev) +{ + return mtk_common_clk_init(dev, &mt8516_clk_tree); +} + +static int mt8516_topckgen_probe(struct udevice *dev) +{ + return mtk_common_clk_init(dev, &mt8516_clk_tree); +} + +static int mt8516_topckgen_cg_probe(struct udevice *dev) +{ + return mtk_common_clk_gate_init(dev, &mt8516_clk_tree, top_clks); +} + +static const struct udevice_id mt8516_apmixed_compat[] = { + { .compatible = "mediatek,mt8516-apmixedsys", }, + { } +}; + +static const struct udevice_id mt8516_topckgen_compat[] = { + { .compatible = "mediatek,mt8516-topckgen", }, + { } +}; + +static const struct udevice_id mt8516_topckgen_cg_compat[] = { + { .compatible = "mediatek,mt8516-topckgen-cg", }, + { } +}; + +U_BOOT_DRIVER(mtk_clk_apmixedsys) = { + .name = "mt8516-apmixedsys", + .id = UCLASS_CLK, + .of_match = mt8516_apmixed_compat, + .probe = mt8516_apmixedsys_probe, + .priv_auto_alloc_size = sizeof(struct mtk_clk_priv), + .ops = &mtk_clk_apmixedsys_ops, + .flags = DM_FLAG_PRE_RELOC, +}; + +U_BOOT_DRIVER(mtk_clk_topckgen) = { + .name = "mt8516-topckgen", + .id = UCLASS_CLK, + .of_match = mt8516_topckgen_compat, + .probe = mt8516_topckgen_probe, + .priv_auto_alloc_size = sizeof(struct mtk_clk_priv), + .ops = &mtk_clk_topckgen_ops, + .flags = DM_FLAG_PRE_RELOC, +}; + +U_BOOT_DRIVER(mtk_clk_topckgen_cg) = { + .name = "mt8516-topckgen-cg", + .id = UCLASS_CLK, + .of_match = mt8516_topckgen_cg_compat, + .probe = mt8516_topckgen_cg_probe, + .priv_auto_alloc_size = sizeof(struct mtk_cg_priv), + .ops = &mtk_clk_gate_ops, + .flags = DM_FLAG_PRE_RELOC, +}; diff --git a/drivers/clk/mediatek/clk-mtk.c b/drivers/clk/mediatek/clk-mtk.c index 870b14ed8b..6c6b500d9b 100644 --- a/drivers/clk/mediatek/clk-mtk.c +++ b/drivers/clk/mediatek/clk-mtk.c @@ -390,6 +390,12 @@ static int mtk_clk_gate_enable(struct clk *clk) case CLK_GATE_SETCLR: writel(bit, priv->base + gate->regs->clr_ofs); break; + case CLK_GATE_SETCLR_INV: + writel(bit, priv->base + gate->regs->set_ofs); + break; + case CLK_GATE_NO_SETCLR: + clrsetbits_le32(priv->base + gate->regs->sta_ofs, bit, 0); + break; case CLK_GATE_NO_SETCLR_INV: clrsetbits_le32(priv->base + gate->regs->sta_ofs, bit, bit); break; @@ -411,6 +417,12 @@ static int mtk_clk_gate_disable(struct clk *clk) case CLK_GATE_SETCLR: writel(bit, priv->base + gate->regs->set_ofs); break; + case CLK_GATE_SETCLR_INV: + writel(bit, priv->base + gate->regs->clr_ofs); + break; + case CLK_GATE_NO_SETCLR: + clrsetbits_le32(priv->base + gate->regs->sta_ofs, bit, bit); + break; case CLK_GATE_NO_SETCLR_INV: clrsetbits_le32(priv->base + gate->regs->sta_ofs, bit, 0); break; diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 5da5c4af39..5643939348 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -28,6 +28,13 @@ config LED_BCM6358 LED HW controller accessed via MMIO registers. HW has no blinking capabilities and up to 32 LEDs can be controlled. +config LED_BCM6858 + bool "LED Support for BCM6858" + depends on LED && (ARCH_BCM6858 || ARCH_BCM63158) + help + This option enables support for LEDs connected to the BCM6858 + HW has blinking capabilities and up to 32 LEDs can be controlled. + config LED_BLINK bool "Support LED blinking" depends on LED diff --git a/drivers/led/Makefile b/drivers/led/Makefile index 160a8f3ae8..3654dd3c04 100644 --- a/drivers/led/Makefile +++ b/drivers/led/Makefile @@ -6,4 +6,5 @@ obj-y += led-uclass.o obj-$(CONFIG_LED_BCM6328) += led_bcm6328.o obj-$(CONFIG_LED_BCM6358) += led_bcm6358.o +obj-$(CONFIG_LED_BCM6858) += led_bcm6858.o obj-$(CONFIG_$(SPL_)LED_GPIO) += led_gpio.o diff --git a/drivers/led/led_bcm6858.c b/drivers/led/led_bcm6858.c new file mode 100644 index 0000000000..27a76fcaf0 --- /dev/null +++ b/drivers/led/led_bcm6858.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 Philippe Reynes <philippe.reynes@softathome.com> + * + * based on: + * drivers/led/led_bcm6328.c + * drivers/led/led_bcm6358.c + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <led.h> +#include <asm/io.h> +#include <dm/lists.h> + +#define LEDS_MAX 32 +#define LEDS_WAIT 100 + +/* LED Mode register */ +#define LED_MODE_REG 0x0 +#define LED_MODE_OFF 0 +#define LED_MODE_ON 1 +#define LED_MODE_MASK 1 + +/* LED Controller Global settings register */ +#define LED_CTRL_REG 0x00 +#define LED_CTRL_MASK 0x1f +#define LED_CTRL_LED_TEST_MODE BIT(0) +#define LED_CTRL_SERIAL_LED_DATA_PPOL BIT(1) +#define LED_CTRL_SERIAL_LED_CLK_POL BIT(2) +#define LED_CTRL_SERIAL_LED_EN_POL BIT(3) +#define LED_CTRL_SERIAL_LED_MSB_FIRST BIT(4) + +/* LED Controller IP LED source select register */ +#define LED_HW_LED_EN_REG 0x08 +/* LED Flash control register0 */ +#define LED_FLASH_RATE_CONTROL_REG0 0x10 +/* Soft LED input register */ +#define LED_SW_LED_IP_REG 0xb8 +/* Soft LED input polarity register */ +#define LED_SW_LED_IP_PPOL_REG 0xbc + +struct bcm6858_led_priv { + void __iomem *regs; + u8 pin; +}; + +#ifdef CONFIG_LED_BLINK +/* + * The value for flash rate are: + * 0 : no blinking + * 1 : rate is 25 Hz => 40 ms (period) + * 2 : rate is 12.5 Hz => 80 ms (period) + * 3 : rate is 6.25 Hz => 160 ms (period) + * 4 : rate is 3.125 Hz => 320 ms (period) + * 5 : rate is 1.5625 Hz => 640 ms (period) + * 6 : rate is 0.7815 Hz => 1280 ms (period) + * 7 : rate is 0.390625 Hz => 2560 ms (period) + */ +static const int bcm6858_flash_rate[8] = { + 0, 40, 80, 160, 320, 640, 1280, 2560 +}; + +static u32 bcm6858_flash_rate_value(int period_ms) +{ + unsigned long value = 7; + int i; + + for (i = 0; i < ARRAY_SIZE(bcm6858_flash_rate); i++) { + if (period_ms <= bcm6858_flash_rate[i]) { + value = i; + break; + } + } + + return value; +} + +static int bcm6858_led_set_period(struct udevice *dev, int period_ms) +{ + struct bcm6858_led_priv *priv = dev_get_priv(dev); + u32 offset, shift, mask, value; + + offset = (priv->pin / 8) * 4; + shift = (priv->pin % 8) * 4; + mask = 0x7 << shift; + value = bcm6858_flash_rate_value(period_ms) << shift; + + clrbits_32(priv->regs + LED_FLASH_RATE_CONTROL_REG0 + offset, mask); + setbits_32(priv->regs + LED_FLASH_RATE_CONTROL_REG0 + offset, value); + + return 0; +} +#endif + +static enum led_state_t bcm6858_led_get_state(struct udevice *dev) +{ + struct bcm6858_led_priv *priv = dev_get_priv(dev); + enum led_state_t state = LEDST_OFF; + u32 sw_led_ip; + + sw_led_ip = readl(priv->regs + LED_SW_LED_IP_REG); + if (sw_led_ip & (1 << priv->pin)) + state = LEDST_ON; + + return state; +} + +static int bcm6858_led_set_state(struct udevice *dev, enum led_state_t state) +{ + struct bcm6858_led_priv *priv = dev_get_priv(dev); + + switch (state) { + case LEDST_OFF: + clrbits_32(priv->regs + LED_SW_LED_IP_REG, (1 << priv->pin)); +#ifdef CONFIG_LED_BLINK + bcm6858_led_set_period(dev, 0); +#endif + break; + case LEDST_ON: + setbits_32(priv->regs + LED_SW_LED_IP_REG, (1 << priv->pin)); +#ifdef CONFIG_LED_BLINK + bcm6858_led_set_period(dev, 0); +#endif + break; + case LEDST_TOGGLE: + if (bcm6858_led_get_state(dev) == LEDST_OFF) + return bcm6858_led_set_state(dev, LEDST_ON); + else + return bcm6858_led_set_state(dev, LEDST_OFF); + break; +#ifdef CONFIG_LED_BLINK + case LEDST_BLINK: + setbits_32(priv->regs + LED_SW_LED_IP_REG, (1 << priv->pin)); + break; +#endif + default: + return -EINVAL; + } + + return 0; +} + +static const struct led_ops bcm6858_led_ops = { + .get_state = bcm6858_led_get_state, + .set_state = bcm6858_led_set_state, +#ifdef CONFIG_LED_BLINK + .set_period = bcm6858_led_set_period, +#endif +}; + +static int bcm6858_led_probe(struct udevice *dev) +{ + struct led_uc_plat *uc_plat = dev_get_uclass_platdata(dev); + + /* Top-level LED node */ + if (!uc_plat->label) { + void __iomem *regs; + u32 set_bits = 0; + + regs = dev_remap_addr(dev); + if (!regs) + return -EINVAL; + + if (dev_read_bool(dev, "brcm,serial-led-msb-first")) + set_bits |= LED_CTRL_SERIAL_LED_MSB_FIRST; + if (dev_read_bool(dev, "brcm,serial-led-en-pol")) + set_bits |= LED_CTRL_SERIAL_LED_EN_POL; + if (dev_read_bool(dev, "brcm,serial-led-clk-pol")) + set_bits |= LED_CTRL_SERIAL_LED_CLK_POL; + if (dev_read_bool(dev, "brcm,serial-led-data-ppol")) + set_bits |= LED_CTRL_SERIAL_LED_DATA_PPOL; + if (dev_read_bool(dev, "brcm,led-test-mode")) + set_bits |= LED_CTRL_LED_TEST_MODE; + + clrsetbits_32(regs + LED_CTRL_REG, ~0, set_bits); + } else { + struct bcm6858_led_priv *priv = dev_get_priv(dev); + void __iomem *regs; + unsigned int pin; + + regs = dev_remap_addr(dev_get_parent(dev)); + if (!regs) + return -EINVAL; + + pin = dev_read_u32_default(dev, "reg", LEDS_MAX); + if (pin >= LEDS_MAX) + return -EINVAL; + + priv->regs = regs; + priv->pin = pin; + + /* this led is managed by software */ + clrbits_32(regs + LED_HW_LED_EN_REG, 1 << pin); + + /* configure the polarity */ + if (dev_read_bool(dev, "active-low")) + clrbits_32(regs + LED_SW_LED_IP_PPOL_REG, 1 << pin); + else + setbits_32(regs + LED_SW_LED_IP_PPOL_REG, 1 << pin); + } + + return 0; +} + +static int bcm6858_led_bind(struct udevice *parent) +{ + ofnode node; + + dev_for_each_subnode(node, parent) { + struct led_uc_plat *uc_plat; + struct udevice *dev; + const char *label; + int ret; + + label = ofnode_read_string(node, "label"); + if (!label) { + debug("%s: node %s has no label\n", __func__, + ofnode_get_name(node)); + return -EINVAL; + } + + ret = device_bind_driver_to_node(parent, "bcm6858-led", + ofnode_get_name(node), + node, &dev); + if (ret) + return ret; + + uc_plat = dev_get_uclass_platdata(dev); + uc_plat->label = label; + } + + return 0; +} + +static const struct udevice_id bcm6858_led_ids[] = { + { .compatible = "brcm,bcm6858-leds" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(bcm6858_led) = { + .name = "bcm6858-led", + .id = UCLASS_LED, + .of_match = bcm6858_led_ids, + .bind = bcm6858_led_bind, + .probe = bcm6858_led_probe, + .priv_auto_alloc_size = sizeof(struct bcm6858_led_priv), + .ops = &bcm6858_led_ops, +}; diff --git a/drivers/misc/fs_loader.c b/drivers/misc/fs_loader.c index 57a14a3479..f42eeff8f6 100644 --- a/drivers/misc/fs_loader.c +++ b/drivers/misc/fs_loader.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (C) 2018 Intel Corporation <www.intel.com> + * Copyright (C) 2018-2019 Intel Corporation <www.intel.com> * */ #include <common.h> @@ -219,32 +219,26 @@ int request_firmware_into_buf(struct udevice *dev, static int fs_loader_ofdata_to_platdata(struct udevice *dev) { - const char *fs_loader_path; u32 phandlepart[2]; - fs_loader_path = ofnode_get_chosen_prop("firmware-loader"); + ofnode fs_loader_node = dev_ofnode(dev); - if (fs_loader_path) { - ofnode fs_loader_node; + if (ofnode_valid(fs_loader_node)) { + struct device_platdata *plat; - fs_loader_node = ofnode_path(fs_loader_path); - if (ofnode_valid(fs_loader_node)) { - struct device_platdata *plat; - plat = dev->platdata; - - if (!ofnode_read_u32_array(fs_loader_node, - "phandlepart", - phandlepart, 2)) { - plat->phandlepart.phandle = phandlepart[0]; - plat->phandlepart.partition = phandlepart[1]; - } + plat = dev->platdata; + if (!ofnode_read_u32_array(fs_loader_node, + "phandlepart", + phandlepart, 2)) { + plat->phandlepart.phandle = phandlepart[0]; + plat->phandlepart.partition = phandlepart[1]; + } - plat->mtdpart = (char *)ofnode_read_string( - fs_loader_node, "mtdpart"); + plat->mtdpart = (char *)ofnode_read_string( + fs_loader_node, "mtdpart"); - plat->ubivol = (char *)ofnode_read_string( - fs_loader_node, "ubivol"); - } + plat->ubivol = (char *)ofnode_read_string( + fs_loader_node, "ubivol"); } return 0; @@ -252,6 +246,29 @@ static int fs_loader_ofdata_to_platdata(struct udevice *dev) static int fs_loader_probe(struct udevice *dev) { +#if CONFIG_IS_ENABLED(DM) && CONFIG_IS_ENABLED(BLK) + int ret; + struct device_platdata *plat = dev->platdata; + + if (plat->phandlepart.phandle) { + ofnode node = ofnode_get_by_phandle(plat->phandlepart.phandle); + struct udevice *parent_dev = NULL; + + ret = device_get_global_by_ofnode(node, &parent_dev); + if (!ret) { + struct udevice *dev; + + ret = blk_get_from_parent(parent_dev, &dev); + if (ret) { + debug("fs_loader: No block device: %d\n", + ret); + + return ret; + } + } + } +#endif + return 0; }; diff --git a/drivers/mmc/mtk-sd.c b/drivers/mmc/mtk-sd.c index d3f0778368..e0ac3e9d69 100644 --- a/drivers/mmc/mtk-sd.c +++ b/drivers/mmc/mtk-sd.c @@ -247,6 +247,7 @@ struct msdc_host { struct msdc_compatible *dev_comp; struct clk src_clk; /* for SD/MMC bus clock */ + struct clk src_clk_cg; /* optional, MSDC source clock control gate */ struct clk h_clk; /* MSDC core clock */ u32 src_clk_freq; /* source clock */ @@ -269,7 +270,7 @@ struct msdc_host { bool builtin_cd; /* card detection / write protection GPIOs */ -#if IS_ENABLED(DM_GPIO) +#if CONFIG_IS_ENABLED(DM_GPIO) struct gpio_desc gpio_wp; struct gpio_desc gpio_cd; #endif @@ -849,7 +850,7 @@ static int msdc_ops_get_cd(struct udevice *dev) return !(val & MSDC_PS_CDSTS); } -#if IS_ENABLED(DM_GPIO) +#if CONFIG_IS_ENABLED(DM_GPIO) if (!host->gpio_cd.dev) return 1; @@ -861,7 +862,7 @@ static int msdc_ops_get_cd(struct udevice *dev) static int msdc_ops_get_wp(struct udevice *dev) { -#if IS_ENABLED(DM_GPIO) +#if CONFIG_IS_ENABLED(DM_GPIO) struct msdc_host *host = dev_get_priv(dev); if (!host->gpio_wp.dev) @@ -1269,6 +1270,8 @@ static void msdc_ungate_clock(struct msdc_host *host) { clk_enable(&host->src_clk); clk_enable(&host->h_clk); + if (host->src_clk_cg.dev) + clk_enable(&host->src_clk_cg); } static int msdc_drv_probe(struct udevice *dev) @@ -1332,7 +1335,9 @@ static int msdc_ofdata_to_platdata(struct udevice *dev) if (ret < 0) return ret; -#if IS_ENABLED(DM_GPIO) + clk_get_by_name(dev, "source_cg", &host->src_clk_cg); /* optional */ + +#if CONFIG_IS_ENABLED(DM_GPIO) gpio_request_by_name(dev, "wp-gpios", 0, &host->gpio_wp, GPIOD_IS_IN); gpio_request_by_name(dev, "cd-gpios", 0, &host->gpio_cd, GPIOD_IS_IN); #endif @@ -1376,8 +1381,18 @@ static const struct msdc_compatible mt7623_compat = { .enhance_rx = false }; +static const struct msdc_compatible mt8516_compat = { + .clk_div_bits = 12, + .pad_tune0 = true, + .async_fifo = true, + .data_tune = true, + .busy_check = true, + .stop_clk_fix = true, +}; + static const struct udevice_id msdc_ids[] = { { .compatible = "mediatek,mt7623-mmc", .data = (ulong)&mt7623_compat }, + { .compatible = "mediatek,mt8516-mmc", .data = (ulong)&mt8516_compat }, {} }; diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index dc087ab641..f86035bcce 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -60,6 +60,31 @@ config SPL_GENERATE_ATMEL_PMECC_HEADER endif +config NAND_BRCMNAND + bool "Support Broadcom NAND controller" + depends on OF_CONTROL && DM && MTD + help + Enable the driver for NAND flash on platforms using a Broadcom NAND + controller. + +config NAND_BRCMNAND_6838 + bool "Support Broadcom NAND controller on bcm6838" + depends on NAND_BRCMNAND && ARCH_BMIPS && SOC_BMIPS_BCM6838 + help + Enable support for broadcom nand driver on bcm6838. + +config NAND_BRCMNAND_6858 + bool "Support Broadcom NAND controller on bcm6858" + depends on NAND_BRCMNAND && ARCH_BCM6858 + help + Enable support for broadcom nand driver on bcm6858. + +config NAND_BRCMNAND_63158 + bool "Support Broadcom NAND controller on bcm63158" + depends on NAND_BRCMNAND && ARCH_BCM63158 + help + Enable support for broadcom nand driver on bcm63158. + config NAND_DAVINCI bool "Support TI Davinci NAND controller" help diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index b10e718d15..9337f6482e 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_NAND_ECC_BCH) += nand_bch.o obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o obj-$(CONFIG_NAND_ARASAN) += arasan_nfc.o +obj-$(CONFIG_NAND_BRCMNAND) += brcmnand/ obj-$(CONFIG_NAND_DAVINCI) += davinci_nand.o obj-$(CONFIG_NAND_DENALI) += denali.o obj-$(CONFIG_NAND_DENALI_DT) += denali_dt.o diff --git a/drivers/mtd/nand/raw/brcmnand/Makefile b/drivers/mtd/nand/raw/brcmnand/Makefile new file mode 100644 index 0000000000..a2363cc80e --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_NAND_BRCMNAND_63158) += bcm63158_nand.o +obj-$(CONFIG_NAND_BRCMNAND_6838) += bcm6838_nand.o +obj-$(CONFIG_NAND_BRCMNAND_6858) += bcm6858_nand.o +obj-$(CONFIG_NAND_BRCMNAND) += brcmnand.o +obj-$(CONFIG_NAND_BRCMNAND) += brcmnand_compat.o diff --git a/drivers/mtd/nand/raw/brcmnand/bcm63158_nand.c b/drivers/mtd/nand/raw/brcmnand/bcm63158_nand.c new file mode 100644 index 0000000000..16b0d4440a --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/bcm63158_nand.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <common.h> +#include <asm/io.h> +#include <memalign.h> +#include <nand.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <dm.h> + +#include "brcmnand.h" + +struct bcm63158_nand_soc { + struct brcmnand_soc soc; + void __iomem *base; +}; + +#define BCM63158_NAND_INT 0x00 +#define BCM63158_NAND_STATUS_SHIFT 0 +#define BCM63158_NAND_STATUS_MASK (0xfff << BCM63158_NAND_STATUS_SHIFT) + +#define BCM63158_NAND_INT_EN 0x04 +#define BCM63158_NAND_ENABLE_SHIFT 0 +#define BCM63158_NAND_ENABLE_MASK (0xffff << BCM63158_NAND_ENABLE_SHIFT) + +enum { + BCM63158_NP_READ = BIT(0), + BCM63158_BLOCK_ERASE = BIT(1), + BCM63158_COPY_BACK = BIT(2), + BCM63158_PAGE_PGM = BIT(3), + BCM63158_CTRL_READY = BIT(4), + BCM63158_DEV_RBPIN = BIT(5), + BCM63158_ECC_ERR_UNC = BIT(6), + BCM63158_ECC_ERR_CORR = BIT(7), +}; + +static bool bcm63158_nand_intc_ack(struct brcmnand_soc *soc) +{ + struct bcm63158_nand_soc *priv = + container_of(soc, struct bcm63158_nand_soc, soc); + void __iomem *mmio = priv->base + BCM63158_NAND_INT; + u32 val = brcmnand_readl(mmio); + + if (val & (BCM63158_CTRL_READY << BCM63158_NAND_STATUS_SHIFT)) { + /* Ack interrupt */ + val &= ~BCM63158_NAND_STATUS_MASK; + val |= BCM63158_CTRL_READY << BCM63158_NAND_STATUS_SHIFT; + brcmnand_writel(val, mmio); + return true; + } + + return false; +} + +static void bcm63158_nand_intc_set(struct brcmnand_soc *soc, bool en) +{ + struct bcm63158_nand_soc *priv = + container_of(soc, struct bcm63158_nand_soc, soc); + void __iomem *mmio = priv->base + BCM63158_NAND_INT_EN; + u32 val = brcmnand_readl(mmio); + + /* Don't ack any interrupts */ + val &= ~BCM63158_NAND_STATUS_MASK; + + if (en) + val |= BCM63158_CTRL_READY << BCM63158_NAND_ENABLE_SHIFT; + else + val &= ~(BCM63158_CTRL_READY << BCM63158_NAND_ENABLE_SHIFT); + + brcmnand_writel(val, mmio); +} + +static int bcm63158_nand_probe(struct udevice *dev) +{ + struct udevice *pdev = dev; + struct bcm63158_nand_soc *priv = dev_get_priv(dev); + struct brcmnand_soc *soc; + struct resource res; + + soc = &priv->soc; + + dev_read_resource_byname(pdev, "nand-int-base", &res); + priv->base = devm_ioremap(dev, res.start, resource_size(&res)); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + soc->ctlrdy_ack = bcm63158_nand_intc_ack; + soc->ctlrdy_set_enabled = bcm63158_nand_intc_set; + + /* Disable and ack all interrupts */ + brcmnand_writel(0, priv->base + BCM63158_NAND_INT_EN); + brcmnand_writel(0, priv->base + BCM63158_NAND_INT); + + return brcmnand_probe(pdev, soc); +} + +static const struct udevice_id bcm63158_nand_dt_ids[] = { + { + .compatible = "brcm,nand-bcm63158", + }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(bcm63158_nand) = { + .name = "bcm63158-nand", + .id = UCLASS_MTD, + .of_match = bcm63158_nand_dt_ids, + .probe = bcm63158_nand_probe, + .priv_auto_alloc_size = sizeof(struct bcm63158_nand_soc), +}; + +void board_nand_init(void) +{ + struct udevice *dev; + int ret; + + ret = uclass_get_device_by_driver(UCLASS_MTD, + DM_GET_DRIVER(bcm63158_nand), &dev); + if (ret && ret != -ENODEV) + pr_err("Failed to initialize %s. (error %d)\n", dev->name, + ret); +} diff --git a/drivers/mtd/nand/raw/brcmnand/bcm6838_nand.c b/drivers/mtd/nand/raw/brcmnand/bcm6838_nand.c new file mode 100644 index 0000000000..ece944485c --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/bcm6838_nand.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <common.h> +#include <asm/io.h> +#include <memalign.h> +#include <nand.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <dm.h> + +#include "brcmnand.h" + +struct bcm6838_nand_soc { + struct brcmnand_soc soc; + void __iomem *base; +}; + +#define BCM6838_NAND_INT 0x00 +#define BCM6838_NAND_STATUS_SHIFT 0 +#define BCM6838_NAND_STATUS_MASK (0xfff << BCM6838_NAND_STATUS_SHIFT) +#define BCM6838_NAND_ENABLE_SHIFT 16 +#define BCM6838_NAND_ENABLE_MASK (0xffff << BCM6838_NAND_ENABLE_SHIFT) + +enum { + BCM6838_NP_READ = BIT(0), + BCM6838_BLOCK_ERASE = BIT(1), + BCM6838_COPY_BACK = BIT(2), + BCM6838_PAGE_PGM = BIT(3), + BCM6838_CTRL_READY = BIT(4), + BCM6838_DEV_RBPIN = BIT(5), + BCM6838_ECC_ERR_UNC = BIT(6), + BCM6838_ECC_ERR_CORR = BIT(7), +}; + +static bool bcm6838_nand_intc_ack(struct brcmnand_soc *soc) +{ + struct bcm6838_nand_soc *priv = + container_of(soc, struct bcm6838_nand_soc, soc); + void __iomem *mmio = priv->base + BCM6838_NAND_INT; + u32 val = brcmnand_readl(mmio); + + if (val & (BCM6838_CTRL_READY << BCM6838_NAND_STATUS_SHIFT)) { + /* Ack interrupt */ + val &= ~BCM6838_NAND_STATUS_MASK; + val |= BCM6838_CTRL_READY << BCM6838_NAND_STATUS_SHIFT; + brcmnand_writel(val, mmio); + return true; + } + + return false; +} + +static void bcm6838_nand_intc_set(struct brcmnand_soc *soc, bool en) +{ + struct bcm6838_nand_soc *priv = + container_of(soc, struct bcm6838_nand_soc, soc); + void __iomem *mmio = priv->base + BCM6838_NAND_INT; + u32 val = brcmnand_readl(mmio); + + /* Don't ack any interrupts */ + val &= ~BCM6838_NAND_STATUS_MASK; + + if (en) + val |= BCM6838_CTRL_READY << BCM6838_NAND_ENABLE_SHIFT; + else + val &= ~(BCM6838_CTRL_READY << BCM6838_NAND_ENABLE_SHIFT); + + brcmnand_writel(val, mmio); +} + +static int bcm6838_nand_probe(struct udevice *dev) +{ + struct udevice *pdev = dev; + struct bcm6838_nand_soc *priv = dev_get_priv(dev); + struct brcmnand_soc *soc; + struct resource res; + + soc = &priv->soc; + + dev_read_resource_byname(pdev, "nand-int-base", &res); + priv->base = ioremap(res.start, resource_size(&res)); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + soc->ctlrdy_ack = bcm6838_nand_intc_ack; + soc->ctlrdy_set_enabled = bcm6838_nand_intc_set; + + /* Disable and ack all interrupts */ + brcmnand_writel(0, priv->base + BCM6838_NAND_INT); + brcmnand_writel(BCM6838_NAND_STATUS_MASK, + priv->base + BCM6838_NAND_INT); + + return brcmnand_probe(pdev, soc); +} + +static const struct udevice_id bcm6838_nand_dt_ids[] = { + { + .compatible = "brcm,nand-bcm6838", + }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(bcm6838_nand) = { + .name = "bcm6838-nand", + .id = UCLASS_MTD, + .of_match = bcm6838_nand_dt_ids, + .probe = bcm6838_nand_probe, + .priv_auto_alloc_size = sizeof(struct bcm6838_nand_soc), +}; + +void board_nand_init(void) +{ + struct udevice *dev; + int ret; + + ret = uclass_get_device_by_driver(UCLASS_MTD, + DM_GET_DRIVER(bcm6838_nand), &dev); + if (ret && ret != -ENODEV) + pr_err("Failed to initialize %s. (error %d)\n", dev->name, + ret); +} diff --git a/drivers/mtd/nand/raw/brcmnand/bcm6858_nand.c b/drivers/mtd/nand/raw/brcmnand/bcm6858_nand.c new file mode 100644 index 0000000000..3586baa4fa --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/bcm6858_nand.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <common.h> +#include <asm/io.h> +#include <memalign.h> +#include <nand.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <dm.h> + +#include "brcmnand.h" + +struct bcm6858_nand_soc { + struct brcmnand_soc soc; + void __iomem *base; +}; + +#define BCM6858_NAND_INT 0x00 +#define BCM6858_NAND_STATUS_SHIFT 0 +#define BCM6858_NAND_STATUS_MASK (0xfff << BCM6858_NAND_STATUS_SHIFT) + +#define BCM6858_NAND_INT_EN 0x04 +#define BCM6858_NAND_ENABLE_SHIFT 0 +#define BCM6858_NAND_ENABLE_MASK (0xffff << BCM6858_NAND_ENABLE_SHIFT) + +enum { + BCM6858_NP_READ = BIT(0), + BCM6858_BLOCK_ERASE = BIT(1), + BCM6858_COPY_BACK = BIT(2), + BCM6858_PAGE_PGM = BIT(3), + BCM6858_CTRL_READY = BIT(4), + BCM6858_DEV_RBPIN = BIT(5), + BCM6858_ECC_ERR_UNC = BIT(6), + BCM6858_ECC_ERR_CORR = BIT(7), +}; + +static bool bcm6858_nand_intc_ack(struct brcmnand_soc *soc) +{ + struct bcm6858_nand_soc *priv = + container_of(soc, struct bcm6858_nand_soc, soc); + void __iomem *mmio = priv->base + BCM6858_NAND_INT; + u32 val = brcmnand_readl(mmio); + + if (val & (BCM6858_CTRL_READY << BCM6858_NAND_STATUS_SHIFT)) { + /* Ack interrupt */ + val &= ~BCM6858_NAND_STATUS_MASK; + val |= BCM6858_CTRL_READY << BCM6858_NAND_STATUS_SHIFT; + brcmnand_writel(val, mmio); + return true; + } + + return false; +} + +static void bcm6858_nand_intc_set(struct brcmnand_soc *soc, bool en) +{ + struct bcm6858_nand_soc *priv = + container_of(soc, struct bcm6858_nand_soc, soc); + void __iomem *mmio = priv->base + BCM6858_NAND_INT_EN; + u32 val = brcmnand_readl(mmio); + + /* Don't ack any interrupts */ + val &= ~BCM6858_NAND_STATUS_MASK; + + if (en) + val |= BCM6858_CTRL_READY << BCM6858_NAND_ENABLE_SHIFT; + else + val &= ~(BCM6858_CTRL_READY << BCM6858_NAND_ENABLE_SHIFT); + + brcmnand_writel(val, mmio); +} + +static int bcm6858_nand_probe(struct udevice *dev) +{ + struct udevice *pdev = dev; + struct bcm6858_nand_soc *priv = dev_get_priv(dev); + struct brcmnand_soc *soc; + struct resource res; + + soc = &priv->soc; + + dev_read_resource_byname(pdev, "nand-int-base", &res); + priv->base = devm_ioremap(dev, res.start, resource_size(&res)); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + soc->ctlrdy_ack = bcm6858_nand_intc_ack; + soc->ctlrdy_set_enabled = bcm6858_nand_intc_set; + + /* Disable and ack all interrupts */ + brcmnand_writel(0, priv->base + BCM6858_NAND_INT_EN); + brcmnand_writel(0, priv->base + BCM6858_NAND_INT); + + return brcmnand_probe(pdev, soc); +} + +static const struct udevice_id bcm6858_nand_dt_ids[] = { + { + .compatible = "brcm,nand-bcm6858", + }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(bcm6858_nand) = { + .name = "bcm6858-nand", + .id = UCLASS_MTD, + .of_match = bcm6858_nand_dt_ids, + .probe = bcm6858_nand_probe, + .priv_auto_alloc_size = sizeof(struct bcm6858_nand_soc), +}; + +void board_nand_init(void) +{ + struct udevice *dev; + int ret; + + ret = uclass_get_device_by_driver(UCLASS_MTD, + DM_GET_DRIVER(bcm6858_nand), &dev); + if (ret && ret != -ENODEV) + pr_err("Failed to initialize %s. (error %d)\n", dev->name, + ret); +} diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c new file mode 100644 index 0000000000..faa6da42d5 --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c @@ -0,0 +1,2805 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright © 2010-2015 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <common.h> +#include <asm/io.h> +#include <memalign.h> +#include <nand.h> +#include <clk.h> +#include <linux/ioport.h> +#include <linux/completion.h> +#include <linux/errno.h> +#include <linux/log2.h> +#include <asm/processor.h> +#include <dm.h> + +#include "brcmnand.h" +#include "brcmnand_compat.h" + +/* + * This flag controls if WP stays on between erase/write commands to mitigate + * flash corruption due to power glitches. Values: + * 0: NAND_WP is not used or not available + * 1: NAND_WP is set by default, cleared for erase/write operations + * 2: NAND_WP is always cleared + */ +static int wp_on = 1; +module_param(wp_on, int, 0444); + +/*********************************************************************** + * Definitions + ***********************************************************************/ + +#define DRV_NAME "brcmnand" + +#define CMD_NULL 0x00 +#define CMD_PAGE_READ 0x01 +#define CMD_SPARE_AREA_READ 0x02 +#define CMD_STATUS_READ 0x03 +#define CMD_PROGRAM_PAGE 0x04 +#define CMD_PROGRAM_SPARE_AREA 0x05 +#define CMD_COPY_BACK 0x06 +#define CMD_DEVICE_ID_READ 0x07 +#define CMD_BLOCK_ERASE 0x08 +#define CMD_FLASH_RESET 0x09 +#define CMD_BLOCKS_LOCK 0x0a +#define CMD_BLOCKS_LOCK_DOWN 0x0b +#define CMD_BLOCKS_UNLOCK 0x0c +#define CMD_READ_BLOCKS_LOCK_STATUS 0x0d +#define CMD_PARAMETER_READ 0x0e +#define CMD_PARAMETER_CHANGE_COL 0x0f +#define CMD_LOW_LEVEL_OP 0x10 + +struct brcm_nand_dma_desc { + u32 next_desc; + u32 next_desc_ext; + u32 cmd_irq; + u32 dram_addr; + u32 dram_addr_ext; + u32 tfr_len; + u32 total_len; + u32 flash_addr; + u32 flash_addr_ext; + u32 cs; + u32 pad2[5]; + u32 status_valid; +} __packed; + +/* Bitfields for brcm_nand_dma_desc::status_valid */ +#define FLASH_DMA_ECC_ERROR (1 << 8) +#define FLASH_DMA_CORR_ERROR (1 << 9) + +/* 512B flash cache in the NAND controller HW */ +#define FC_SHIFT 9U +#define FC_BYTES 512U +#define FC_WORDS (FC_BYTES >> 2) + +#define BRCMNAND_MIN_PAGESIZE 512 +#define BRCMNAND_MIN_BLOCKSIZE (8 * 1024) +#define BRCMNAND_MIN_DEVSIZE (4ULL * 1024 * 1024) + +#define NAND_CTRL_RDY (INTFC_CTLR_READY | INTFC_FLASH_READY) +#define NAND_POLL_STATUS_TIMEOUT_MS 100 + +/* Controller feature flags */ +enum { + BRCMNAND_HAS_1K_SECTORS = BIT(0), + BRCMNAND_HAS_PREFETCH = BIT(1), + BRCMNAND_HAS_CACHE_MODE = BIT(2), + BRCMNAND_HAS_WP = BIT(3), +}; + +struct brcmnand_controller { +#ifndef __UBOOT__ + struct device *dev; +#else + struct udevice *dev; +#endif /* __UBOOT__ */ + struct nand_hw_control controller; + void __iomem *nand_base; + void __iomem *nand_fc; /* flash cache */ + void __iomem *flash_dma_base; + unsigned int irq; + unsigned int dma_irq; + int nand_version; + int parameter_page_big_endian; + + /* Some SoCs provide custom interrupt status register(s) */ + struct brcmnand_soc *soc; + + /* Some SoCs have a gateable clock for the controller */ + struct clk *clk; + + int cmd_pending; + bool dma_pending; + struct completion done; + struct completion dma_done; + + /* List of NAND hosts (one for each chip-select) */ + struct list_head host_list; + + struct brcm_nand_dma_desc *dma_desc; + dma_addr_t dma_pa; + + /* in-memory cache of the FLASH_CACHE, used only for some commands */ + u8 flash_cache[FC_BYTES]; + + /* Controller revision details */ + const u16 *reg_offsets; + unsigned int reg_spacing; /* between CS1, CS2, ... regs */ + const u8 *cs_offsets; /* within each chip-select */ + const u8 *cs0_offsets; /* within CS0, if different */ + unsigned int max_block_size; + const unsigned int *block_sizes; + unsigned int max_page_size; + const unsigned int *page_sizes; + unsigned int max_oob; + u32 features; + + /* for low-power standby/resume only */ + u32 nand_cs_nand_select; + u32 nand_cs_nand_xor; + u32 corr_stat_threshold; + u32 flash_dma_mode; +}; + +struct brcmnand_cfg { + u64 device_size; + unsigned int block_size; + unsigned int page_size; + unsigned int spare_area_size; + unsigned int device_width; + unsigned int col_adr_bytes; + unsigned int blk_adr_bytes; + unsigned int ful_adr_bytes; + unsigned int sector_size_1k; + unsigned int ecc_level; + /* use for low-power standby/resume only */ + u32 acc_control; + u32 config; + u32 config_ext; + u32 timing_1; + u32 timing_2; +}; + +struct brcmnand_host { + struct list_head node; + + struct nand_chip chip; +#ifndef __UBOOT__ + struct platform_device *pdev; +#else + struct udevice *pdev; +#endif /* __UBOOT__ */ + int cs; + + unsigned int last_cmd; + unsigned int last_byte; + u64 last_addr; + struct brcmnand_cfg hwcfg; + struct brcmnand_controller *ctrl; +}; + +enum brcmnand_reg { + BRCMNAND_CMD_START = 0, + BRCMNAND_CMD_EXT_ADDRESS, + BRCMNAND_CMD_ADDRESS, + BRCMNAND_INTFC_STATUS, + BRCMNAND_CS_SELECT, + BRCMNAND_CS_XOR, + BRCMNAND_LL_OP, + BRCMNAND_CS0_BASE, + BRCMNAND_CS1_BASE, /* CS1 regs, if non-contiguous */ + BRCMNAND_CORR_THRESHOLD, + BRCMNAND_CORR_THRESHOLD_EXT, + BRCMNAND_UNCORR_COUNT, + BRCMNAND_CORR_COUNT, + BRCMNAND_CORR_EXT_ADDR, + BRCMNAND_CORR_ADDR, + BRCMNAND_UNCORR_EXT_ADDR, + BRCMNAND_UNCORR_ADDR, + BRCMNAND_SEMAPHORE, + BRCMNAND_ID, + BRCMNAND_ID_EXT, + BRCMNAND_LL_RDATA, + BRCMNAND_OOB_READ_BASE, + BRCMNAND_OOB_READ_10_BASE, /* offset 0x10, if non-contiguous */ + BRCMNAND_OOB_WRITE_BASE, + BRCMNAND_OOB_WRITE_10_BASE, /* offset 0x10, if non-contiguous */ + BRCMNAND_FC_BASE, +}; + +/* BRCMNAND v4.0 */ +static const u16 brcmnand_regs_v40[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x6c, + [BRCMNAND_CS_SELECT] = 0x14, + [BRCMNAND_CS_XOR] = 0x18, + [BRCMNAND_LL_OP] = 0x178, + [BRCMNAND_CS0_BASE] = 0x40, + [BRCMNAND_CS1_BASE] = 0xd0, + [BRCMNAND_CORR_THRESHOLD] = 0x84, + [BRCMNAND_CORR_THRESHOLD_EXT] = 0, + [BRCMNAND_UNCORR_COUNT] = 0, + [BRCMNAND_CORR_COUNT] = 0, + [BRCMNAND_CORR_EXT_ADDR] = 0x70, + [BRCMNAND_CORR_ADDR] = 0x74, + [BRCMNAND_UNCORR_EXT_ADDR] = 0x78, + [BRCMNAND_UNCORR_ADDR] = 0x7c, + [BRCMNAND_SEMAPHORE] = 0x58, + [BRCMNAND_ID] = 0x60, + [BRCMNAND_ID_EXT] = 0x64, + [BRCMNAND_LL_RDATA] = 0x17c, + [BRCMNAND_OOB_READ_BASE] = 0x20, + [BRCMNAND_OOB_READ_10_BASE] = 0x130, + [BRCMNAND_OOB_WRITE_BASE] = 0x30, + [BRCMNAND_OOB_WRITE_10_BASE] = 0, + [BRCMNAND_FC_BASE] = 0x200, +}; + +/* BRCMNAND v5.0 */ +static const u16 brcmnand_regs_v50[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x6c, + [BRCMNAND_CS_SELECT] = 0x14, + [BRCMNAND_CS_XOR] = 0x18, + [BRCMNAND_LL_OP] = 0x178, + [BRCMNAND_CS0_BASE] = 0x40, + [BRCMNAND_CS1_BASE] = 0xd0, + [BRCMNAND_CORR_THRESHOLD] = 0x84, + [BRCMNAND_CORR_THRESHOLD_EXT] = 0, + [BRCMNAND_UNCORR_COUNT] = 0, + [BRCMNAND_CORR_COUNT] = 0, + [BRCMNAND_CORR_EXT_ADDR] = 0x70, + [BRCMNAND_CORR_ADDR] = 0x74, + [BRCMNAND_UNCORR_EXT_ADDR] = 0x78, + [BRCMNAND_UNCORR_ADDR] = 0x7c, + [BRCMNAND_SEMAPHORE] = 0x58, + [BRCMNAND_ID] = 0x60, + [BRCMNAND_ID_EXT] = 0x64, + [BRCMNAND_LL_RDATA] = 0x17c, + [BRCMNAND_OOB_READ_BASE] = 0x20, + [BRCMNAND_OOB_READ_10_BASE] = 0x130, + [BRCMNAND_OOB_WRITE_BASE] = 0x30, + [BRCMNAND_OOB_WRITE_10_BASE] = 0x140, + [BRCMNAND_FC_BASE] = 0x200, +}; + +/* BRCMNAND v6.0 - v7.1 */ +static const u16 brcmnand_regs_v60[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x14, + [BRCMNAND_CS_SELECT] = 0x18, + [BRCMNAND_CS_XOR] = 0x1c, + [BRCMNAND_LL_OP] = 0x20, + [BRCMNAND_CS0_BASE] = 0x50, + [BRCMNAND_CS1_BASE] = 0, + [BRCMNAND_CORR_THRESHOLD] = 0xc0, + [BRCMNAND_CORR_THRESHOLD_EXT] = 0xc4, + [BRCMNAND_UNCORR_COUNT] = 0xfc, + [BRCMNAND_CORR_COUNT] = 0x100, + [BRCMNAND_CORR_EXT_ADDR] = 0x10c, + [BRCMNAND_CORR_ADDR] = 0x110, + [BRCMNAND_UNCORR_EXT_ADDR] = 0x114, + [BRCMNAND_UNCORR_ADDR] = 0x118, + [BRCMNAND_SEMAPHORE] = 0x150, + [BRCMNAND_ID] = 0x194, + [BRCMNAND_ID_EXT] = 0x198, + [BRCMNAND_LL_RDATA] = 0x19c, + [BRCMNAND_OOB_READ_BASE] = 0x200, + [BRCMNAND_OOB_READ_10_BASE] = 0, + [BRCMNAND_OOB_WRITE_BASE] = 0x280, + [BRCMNAND_OOB_WRITE_10_BASE] = 0, + [BRCMNAND_FC_BASE] = 0x400, +}; + +/* BRCMNAND v7.1 */ +static const u16 brcmnand_regs_v71[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x14, + [BRCMNAND_CS_SELECT] = 0x18, + [BRCMNAND_CS_XOR] = 0x1c, + [BRCMNAND_LL_OP] = 0x20, + [BRCMNAND_CS0_BASE] = 0x50, + [BRCMNAND_CS1_BASE] = 0, + [BRCMNAND_CORR_THRESHOLD] = 0xdc, + [BRCMNAND_CORR_THRESHOLD_EXT] = 0xe0, + [BRCMNAND_UNCORR_COUNT] = 0xfc, + [BRCMNAND_CORR_COUNT] = 0x100, + [BRCMNAND_CORR_EXT_ADDR] = 0x10c, + [BRCMNAND_CORR_ADDR] = 0x110, + [BRCMNAND_UNCORR_EXT_ADDR] = 0x114, + [BRCMNAND_UNCORR_ADDR] = 0x118, + [BRCMNAND_SEMAPHORE] = 0x150, + [BRCMNAND_ID] = 0x194, + [BRCMNAND_ID_EXT] = 0x198, + [BRCMNAND_LL_RDATA] = 0x19c, + [BRCMNAND_OOB_READ_BASE] = 0x200, + [BRCMNAND_OOB_READ_10_BASE] = 0, + [BRCMNAND_OOB_WRITE_BASE] = 0x280, + [BRCMNAND_OOB_WRITE_10_BASE] = 0, + [BRCMNAND_FC_BASE] = 0x400, +}; + +/* BRCMNAND v7.2 */ +static const u16 brcmnand_regs_v72[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x14, + [BRCMNAND_CS_SELECT] = 0x18, + [BRCMNAND_CS_XOR] = 0x1c, + [BRCMNAND_LL_OP] = 0x20, + [BRCMNAND_CS0_BASE] = 0x50, + [BRCMNAND_CS1_BASE] = 0, + [BRCMNAND_CORR_THRESHOLD] = 0xdc, + [BRCMNAND_CORR_THRESHOLD_EXT] = 0xe0, + [BRCMNAND_UNCORR_COUNT] = 0xfc, + [BRCMNAND_CORR_COUNT] = 0x100, + [BRCMNAND_CORR_EXT_ADDR] = 0x10c, + [BRCMNAND_CORR_ADDR] = 0x110, + [BRCMNAND_UNCORR_EXT_ADDR] = 0x114, + [BRCMNAND_UNCORR_ADDR] = 0x118, + [BRCMNAND_SEMAPHORE] = 0x150, + [BRCMNAND_ID] = 0x194, + [BRCMNAND_ID_EXT] = 0x198, + [BRCMNAND_LL_RDATA] = 0x19c, + [BRCMNAND_OOB_READ_BASE] = 0x200, + [BRCMNAND_OOB_READ_10_BASE] = 0, + [BRCMNAND_OOB_WRITE_BASE] = 0x400, + [BRCMNAND_OOB_WRITE_10_BASE] = 0, + [BRCMNAND_FC_BASE] = 0x600, +}; + +enum brcmnand_cs_reg { + BRCMNAND_CS_CFG_EXT = 0, + BRCMNAND_CS_CFG, + BRCMNAND_CS_ACC_CONTROL, + BRCMNAND_CS_TIMING1, + BRCMNAND_CS_TIMING2, +}; + +/* Per chip-select offsets for v7.1 */ +static const u8 brcmnand_cs_offsets_v71[] = { + [BRCMNAND_CS_ACC_CONTROL] = 0x00, + [BRCMNAND_CS_CFG_EXT] = 0x04, + [BRCMNAND_CS_CFG] = 0x08, + [BRCMNAND_CS_TIMING1] = 0x0c, + [BRCMNAND_CS_TIMING2] = 0x10, +}; + +/* Per chip-select offsets for pre v7.1, except CS0 on <= v5.0 */ +static const u8 brcmnand_cs_offsets[] = { + [BRCMNAND_CS_ACC_CONTROL] = 0x00, + [BRCMNAND_CS_CFG_EXT] = 0x04, + [BRCMNAND_CS_CFG] = 0x04, + [BRCMNAND_CS_TIMING1] = 0x08, + [BRCMNAND_CS_TIMING2] = 0x0c, +}; + +/* Per chip-select offset for <= v5.0 on CS0 only */ +static const u8 brcmnand_cs_offsets_cs0[] = { + [BRCMNAND_CS_ACC_CONTROL] = 0x00, + [BRCMNAND_CS_CFG_EXT] = 0x08, + [BRCMNAND_CS_CFG] = 0x08, + [BRCMNAND_CS_TIMING1] = 0x10, + [BRCMNAND_CS_TIMING2] = 0x14, +}; + +/* + * Bitfields for the CFG and CFG_EXT registers. Pre-v7.1 controllers only had + * one config register, but once the bitfields overflowed, newer controllers + * (v7.1 and newer) added a CFG_EXT register and shuffled a few fields around. + */ +enum { + CFG_BLK_ADR_BYTES_SHIFT = 8, + CFG_COL_ADR_BYTES_SHIFT = 12, + CFG_FUL_ADR_BYTES_SHIFT = 16, + CFG_BUS_WIDTH_SHIFT = 23, + CFG_BUS_WIDTH = BIT(CFG_BUS_WIDTH_SHIFT), + CFG_DEVICE_SIZE_SHIFT = 24, + + /* Only for pre-v7.1 (with no CFG_EXT register) */ + CFG_PAGE_SIZE_SHIFT = 20, + CFG_BLK_SIZE_SHIFT = 28, + + /* Only for v7.1+ (with CFG_EXT register) */ + CFG_EXT_PAGE_SIZE_SHIFT = 0, + CFG_EXT_BLK_SIZE_SHIFT = 4, +}; + +/* BRCMNAND_INTFC_STATUS */ +enum { + INTFC_FLASH_STATUS = GENMASK(7, 0), + + INTFC_ERASED = BIT(27), + INTFC_OOB_VALID = BIT(28), + INTFC_CACHE_VALID = BIT(29), + INTFC_FLASH_READY = BIT(30), + INTFC_CTLR_READY = BIT(31), +}; + +static inline u32 nand_readreg(struct brcmnand_controller *ctrl, u32 offs) +{ + return brcmnand_readl(ctrl->nand_base + offs); +} + +static inline void nand_writereg(struct brcmnand_controller *ctrl, u32 offs, + u32 val) +{ + brcmnand_writel(val, ctrl->nand_base + offs); +} + +static int brcmnand_revision_init(struct brcmnand_controller *ctrl) +{ + static const unsigned int block_sizes_v6[] = { 8, 16, 128, 256, 512, 1024, 2048, 0 }; + static const unsigned int block_sizes_v4[] = { 16, 128, 8, 512, 256, 1024, 2048, 0 }; + static const unsigned int page_sizes[] = { 512, 2048, 4096, 8192, 0 }; + + ctrl->nand_version = nand_readreg(ctrl, 0) & 0xffff; + + /* Only support v4.0+? */ + if (ctrl->nand_version < 0x0400) { + dev_err(ctrl->dev, "version %#x not supported\n", + ctrl->nand_version); + return -ENODEV; + } + + /* Register offsets */ + if (ctrl->nand_version >= 0x0702) + ctrl->reg_offsets = brcmnand_regs_v72; + else if (ctrl->nand_version >= 0x0701) + ctrl->reg_offsets = brcmnand_regs_v71; + else if (ctrl->nand_version >= 0x0600) + ctrl->reg_offsets = brcmnand_regs_v60; + else if (ctrl->nand_version >= 0x0500) + ctrl->reg_offsets = brcmnand_regs_v50; + else if (ctrl->nand_version >= 0x0400) + ctrl->reg_offsets = brcmnand_regs_v40; + + /* Chip-select stride */ + if (ctrl->nand_version >= 0x0701) + ctrl->reg_spacing = 0x14; + else + ctrl->reg_spacing = 0x10; + + /* Per chip-select registers */ + if (ctrl->nand_version >= 0x0701) { + ctrl->cs_offsets = brcmnand_cs_offsets_v71; + } else { + ctrl->cs_offsets = brcmnand_cs_offsets; + + /* v5.0 and earlier has a different CS0 offset layout */ + if (ctrl->nand_version <= 0x0500) + ctrl->cs0_offsets = brcmnand_cs_offsets_cs0; + } + + /* Page / block sizes */ + if (ctrl->nand_version >= 0x0701) { + /* >= v7.1 use nice power-of-2 values! */ + ctrl->max_page_size = 16 * 1024; + ctrl->max_block_size = 2 * 1024 * 1024; + } else { + ctrl->page_sizes = page_sizes; + if (ctrl->nand_version >= 0x0600) + ctrl->block_sizes = block_sizes_v6; + else + ctrl->block_sizes = block_sizes_v4; + + if (ctrl->nand_version < 0x0400) { + ctrl->max_page_size = 4096; + ctrl->max_block_size = 512 * 1024; + } + } + + /* Maximum spare area sector size (per 512B) */ + if (ctrl->nand_version >= 0x0702) + ctrl->max_oob = 128; + else if (ctrl->nand_version >= 0x0600) + ctrl->max_oob = 64; + else if (ctrl->nand_version >= 0x0500) + ctrl->max_oob = 32; + else + ctrl->max_oob = 16; + + /* v6.0 and newer (except v6.1) have prefetch support */ + if (ctrl->nand_version >= 0x0600 && ctrl->nand_version != 0x0601) + ctrl->features |= BRCMNAND_HAS_PREFETCH; + + /* + * v6.x has cache mode, but it's implemented differently. Ignore it for + * now. + */ + if (ctrl->nand_version >= 0x0700) + ctrl->features |= BRCMNAND_HAS_CACHE_MODE; + + if (ctrl->nand_version >= 0x0500) + ctrl->features |= BRCMNAND_HAS_1K_SECTORS; + + if (ctrl->nand_version >= 0x0700) + ctrl->features |= BRCMNAND_HAS_WP; +#ifndef __UBOOT__ + else if (of_property_read_bool(ctrl->dev->of_node, "brcm,nand-has-wp")) +#else + else if (dev_read_bool(ctrl->dev, "brcm,nand-has-wp")) +#endif /* __UBOOT__ */ + ctrl->features |= BRCMNAND_HAS_WP; + + return 0; +} + +static inline u32 brcmnand_read_reg(struct brcmnand_controller *ctrl, + enum brcmnand_reg reg) +{ + u16 offs = ctrl->reg_offsets[reg]; + + if (offs) + return nand_readreg(ctrl, offs); + else + return 0; +} + +static inline void brcmnand_write_reg(struct brcmnand_controller *ctrl, + enum brcmnand_reg reg, u32 val) +{ + u16 offs = ctrl->reg_offsets[reg]; + + if (offs) + nand_writereg(ctrl, offs, val); +} + +static inline void brcmnand_rmw_reg(struct brcmnand_controller *ctrl, + enum brcmnand_reg reg, u32 mask, unsigned + int shift, u32 val) +{ + u32 tmp = brcmnand_read_reg(ctrl, reg); + + tmp &= ~mask; + tmp |= val << shift; + brcmnand_write_reg(ctrl, reg, tmp); +} + +static inline u32 brcmnand_read_fc(struct brcmnand_controller *ctrl, int word) +{ + return __raw_readl(ctrl->nand_fc + word * 4); +} + +static inline void brcmnand_write_fc(struct brcmnand_controller *ctrl, + int word, u32 val) +{ + __raw_writel(val, ctrl->nand_fc + word * 4); +} + +static inline u16 brcmnand_cs_offset(struct brcmnand_controller *ctrl, int cs, + enum brcmnand_cs_reg reg) +{ + u16 offs_cs0 = ctrl->reg_offsets[BRCMNAND_CS0_BASE]; + u16 offs_cs1 = ctrl->reg_offsets[BRCMNAND_CS1_BASE]; + u8 cs_offs; + + if (cs == 0 && ctrl->cs0_offsets) + cs_offs = ctrl->cs0_offsets[reg]; + else + cs_offs = ctrl->cs_offsets[reg]; + + if (cs && offs_cs1) + return offs_cs1 + (cs - 1) * ctrl->reg_spacing + cs_offs; + + return offs_cs0 + cs * ctrl->reg_spacing + cs_offs; +} + +static inline u32 brcmnand_count_corrected(struct brcmnand_controller *ctrl) +{ + if (ctrl->nand_version < 0x0600) + return 1; + return brcmnand_read_reg(ctrl, BRCMNAND_CORR_COUNT); +} + +static void brcmnand_wr_corr_thresh(struct brcmnand_host *host, u8 val) +{ + struct brcmnand_controller *ctrl = host->ctrl; + unsigned int shift = 0, bits; + enum brcmnand_reg reg = BRCMNAND_CORR_THRESHOLD; + int cs = host->cs; + + if (ctrl->nand_version >= 0x0702) + bits = 7; + else if (ctrl->nand_version >= 0x0600) + bits = 6; + else if (ctrl->nand_version >= 0x0500) + bits = 5; + else + bits = 4; + + if (ctrl->nand_version >= 0x0702) { + if (cs >= 4) + reg = BRCMNAND_CORR_THRESHOLD_EXT; + shift = (cs % 4) * bits; + } else if (ctrl->nand_version >= 0x0600) { + if (cs >= 5) + reg = BRCMNAND_CORR_THRESHOLD_EXT; + shift = (cs % 5) * bits; + } + brcmnand_rmw_reg(ctrl, reg, (bits - 1) << shift, shift, val); +} + +static inline int brcmnand_cmd_shift(struct brcmnand_controller *ctrl) +{ + if (ctrl->nand_version < 0x0602) + return 24; + return 0; +} + +/*********************************************************************** + * NAND ACC CONTROL bitfield + * + * Some bits have remained constant throughout hardware revision, while + * others have shifted around. + ***********************************************************************/ + +/* Constant for all versions (where supported) */ +enum { + /* See BRCMNAND_HAS_CACHE_MODE */ + ACC_CONTROL_CACHE_MODE = BIT(22), + + /* See BRCMNAND_HAS_PREFETCH */ + ACC_CONTROL_PREFETCH = BIT(23), + + ACC_CONTROL_PAGE_HIT = BIT(24), + ACC_CONTROL_WR_PREEMPT = BIT(25), + ACC_CONTROL_PARTIAL_PAGE = BIT(26), + ACC_CONTROL_RD_ERASED = BIT(27), + ACC_CONTROL_FAST_PGM_RDIN = BIT(28), + ACC_CONTROL_WR_ECC = BIT(30), + ACC_CONTROL_RD_ECC = BIT(31), +}; + +static inline u32 brcmnand_spare_area_mask(struct brcmnand_controller *ctrl) +{ + if (ctrl->nand_version >= 0x0702) + return GENMASK(7, 0); + else if (ctrl->nand_version >= 0x0600) + return GENMASK(6, 0); + else + return GENMASK(5, 0); +} + +#define NAND_ACC_CONTROL_ECC_SHIFT 16 +#define NAND_ACC_CONTROL_ECC_EXT_SHIFT 13 + +static inline u32 brcmnand_ecc_level_mask(struct brcmnand_controller *ctrl) +{ + u32 mask = (ctrl->nand_version >= 0x0600) ? 0x1f : 0x0f; + + mask <<= NAND_ACC_CONTROL_ECC_SHIFT; + + /* v7.2 includes additional ECC levels */ + if (ctrl->nand_version >= 0x0702) + mask |= 0x7 << NAND_ACC_CONTROL_ECC_EXT_SHIFT; + + return mask; +} + +static void brcmnand_set_ecc_enabled(struct brcmnand_host *host, int en) +{ + struct brcmnand_controller *ctrl = host->ctrl; + u16 offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_ACC_CONTROL); + u32 acc_control = nand_readreg(ctrl, offs); + u32 ecc_flags = ACC_CONTROL_WR_ECC | ACC_CONTROL_RD_ECC; + + if (en) { + acc_control |= ecc_flags; /* enable RD/WR ECC */ + acc_control |= host->hwcfg.ecc_level + << NAND_ACC_CONTROL_ECC_SHIFT; + } else { + acc_control &= ~ecc_flags; /* disable RD/WR ECC */ + acc_control &= ~brcmnand_ecc_level_mask(ctrl); + } + + nand_writereg(ctrl, offs, acc_control); +} + +static inline int brcmnand_sector_1k_shift(struct brcmnand_controller *ctrl) +{ + if (ctrl->nand_version >= 0x0702) + return 9; + else if (ctrl->nand_version >= 0x0600) + return 7; + else if (ctrl->nand_version >= 0x0500) + return 6; + else + return -1; +} + +static int brcmnand_get_sector_size_1k(struct brcmnand_host *host) +{ + struct brcmnand_controller *ctrl = host->ctrl; + int shift = brcmnand_sector_1k_shift(ctrl); + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + + if (shift < 0) + return 0; + + return (nand_readreg(ctrl, acc_control_offs) >> shift) & 0x1; +} + +static void brcmnand_set_sector_size_1k(struct brcmnand_host *host, int val) +{ + struct brcmnand_controller *ctrl = host->ctrl; + int shift = brcmnand_sector_1k_shift(ctrl); + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + u32 tmp; + + if (shift < 0) + return; + + tmp = nand_readreg(ctrl, acc_control_offs); + tmp &= ~(1 << shift); + tmp |= (!!val) << shift; + nand_writereg(ctrl, acc_control_offs, tmp); +} + +/*********************************************************************** + * CS_NAND_SELECT + ***********************************************************************/ + +enum { + CS_SELECT_NAND_WP = BIT(29), + CS_SELECT_AUTO_DEVICE_ID_CFG = BIT(30), +}; + +static int bcmnand_ctrl_poll_status(struct brcmnand_controller *ctrl, + u32 mask, u32 expected_val, + unsigned long timeout_ms) +{ +#ifndef __UBOOT__ + unsigned long limit; + u32 val; + + if (!timeout_ms) + timeout_ms = NAND_POLL_STATUS_TIMEOUT_MS; + + limit = jiffies + msecs_to_jiffies(timeout_ms); + do { + val = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS); + if ((val & mask) == expected_val) + return 0; + + cpu_relax(); + } while (time_after(limit, jiffies)); +#else + unsigned long base, limit; + u32 val; + + if (!timeout_ms) + timeout_ms = NAND_POLL_STATUS_TIMEOUT_MS; + + base = get_timer(0); + limit = CONFIG_SYS_HZ * timeout_ms / 1000; + do { + val = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS); + if ((val & mask) == expected_val) + return 0; + + cpu_relax(); + } while (get_timer(base) < limit); +#endif /* __UBOOT__ */ + + dev_warn(ctrl->dev, "timeout on status poll (expected %x got %x)\n", + expected_val, val & mask); + + return -ETIMEDOUT; +} + +static inline void brcmnand_set_wp(struct brcmnand_controller *ctrl, bool en) +{ + u32 val = en ? CS_SELECT_NAND_WP : 0; + + brcmnand_rmw_reg(ctrl, BRCMNAND_CS_SELECT, CS_SELECT_NAND_WP, 0, val); +} + +/*********************************************************************** + * Flash DMA + ***********************************************************************/ + +enum flash_dma_reg { + FLASH_DMA_REVISION = 0x00, + FLASH_DMA_FIRST_DESC = 0x04, + FLASH_DMA_FIRST_DESC_EXT = 0x08, + FLASH_DMA_CTRL = 0x0c, + FLASH_DMA_MODE = 0x10, + FLASH_DMA_STATUS = 0x14, + FLASH_DMA_INTERRUPT_DESC = 0x18, + FLASH_DMA_INTERRUPT_DESC_EXT = 0x1c, + FLASH_DMA_ERROR_STATUS = 0x20, + FLASH_DMA_CURRENT_DESC = 0x24, + FLASH_DMA_CURRENT_DESC_EXT = 0x28, +}; + +static inline bool has_flash_dma(struct brcmnand_controller *ctrl) +{ + return ctrl->flash_dma_base; +} + +static inline bool flash_dma_buf_ok(const void *buf) +{ +#ifndef __UBOOT__ + return buf && !is_vmalloc_addr(buf) && + likely(IS_ALIGNED((uintptr_t)buf, 4)); +#else + return buf && likely(IS_ALIGNED((uintptr_t)buf, 4)); +#endif /* __UBOOT__ */ +} + +static inline void flash_dma_writel(struct brcmnand_controller *ctrl, u8 offs, + u32 val) +{ + brcmnand_writel(val, ctrl->flash_dma_base + offs); +} + +static inline u32 flash_dma_readl(struct brcmnand_controller *ctrl, u8 offs) +{ + return brcmnand_readl(ctrl->flash_dma_base + offs); +} + +/* Low-level operation types: command, address, write, or read */ +enum brcmnand_llop_type { + LL_OP_CMD, + LL_OP_ADDR, + LL_OP_WR, + LL_OP_RD, +}; + +/*********************************************************************** + * Internal support functions + ***********************************************************************/ + +static inline bool is_hamming_ecc(struct brcmnand_controller *ctrl, + struct brcmnand_cfg *cfg) +{ + if (ctrl->nand_version <= 0x0701) + return cfg->sector_size_1k == 0 && cfg->spare_area_size == 16 && + cfg->ecc_level == 15; + else + return cfg->sector_size_1k == 0 && ((cfg->spare_area_size == 16 && + cfg->ecc_level == 15) || + (cfg->spare_area_size == 28 && cfg->ecc_level == 16)); +} + +/* + * Set mtd->ooblayout to the appropriate mtd_ooblayout_ops given + * the layout/configuration. + * Returns -ERRCODE on failure. + */ +static int brcmnand_hamming_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + int sectors = cfg->page_size / (512 << cfg->sector_size_1k); + + if (section >= sectors) + return -ERANGE; + + oobregion->offset = (section * sas) + 6; + oobregion->length = 3; + + return 0; +} + +static int brcmnand_hamming_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + int sectors = cfg->page_size / (512 << cfg->sector_size_1k); + + if (section >= sectors * 2) + return -ERANGE; + + oobregion->offset = (section / 2) * sas; + + if (section & 1) { + oobregion->offset += 9; + oobregion->length = 7; + } else { + oobregion->length = 6; + + /* First sector of each page may have BBI */ + if (!section) { + /* + * Small-page NAND use byte 6 for BBI while large-page + * NAND use byte 0. + */ + if (cfg->page_size > 512) + oobregion->offset++; + oobregion->length--; + } + } + + return 0; +} + +static const struct mtd_ooblayout_ops brcmnand_hamming_ooblayout_ops = { + .ecc = brcmnand_hamming_ooblayout_ecc, + .free = brcmnand_hamming_ooblayout_free, +}; + +static int brcmnand_bch_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + int sectors = cfg->page_size / (512 << cfg->sector_size_1k); + + if (section >= sectors) + return -ERANGE; + + oobregion->offset = (section * (sas + 1)) - chip->ecc.bytes; + oobregion->length = chip->ecc.bytes; + + return 0; +} + +static int brcmnand_bch_ooblayout_free_lp(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + int sectors = cfg->page_size / (512 << cfg->sector_size_1k); + + if (section >= sectors) + return -ERANGE; + + if (sas <= chip->ecc.bytes) + return 0; + + oobregion->offset = section * sas; + oobregion->length = sas - chip->ecc.bytes; + + if (!section) { + oobregion->offset++; + oobregion->length--; + } + + return 0; +} + +static int brcmnand_bch_ooblayout_free_sp(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + + if (section > 1 || sas - chip->ecc.bytes < 6 || + (section && sas - chip->ecc.bytes == 6)) + return -ERANGE; + + if (!section) { + oobregion->offset = 0; + oobregion->length = 5; + } else { + oobregion->offset = 6; + oobregion->length = sas - chip->ecc.bytes - 6; + } + + return 0; +} + +static const struct mtd_ooblayout_ops brcmnand_bch_lp_ooblayout_ops = { + .ecc = brcmnand_bch_ooblayout_ecc, + .free = brcmnand_bch_ooblayout_free_lp, +}; + +static const struct mtd_ooblayout_ops brcmnand_bch_sp_ooblayout_ops = { + .ecc = brcmnand_bch_ooblayout_ecc, + .free = brcmnand_bch_ooblayout_free_sp, +}; + +static int brcmstb_choose_ecc_layout(struct brcmnand_host *host) +{ + struct brcmnand_cfg *p = &host->hwcfg; + struct mtd_info *mtd = nand_to_mtd(&host->chip); + struct nand_ecc_ctrl *ecc = &host->chip.ecc; + unsigned int ecc_level = p->ecc_level; + int sas = p->spare_area_size << p->sector_size_1k; + int sectors = p->page_size / (512 << p->sector_size_1k); + + if (p->sector_size_1k) + ecc_level <<= 1; + + if (is_hamming_ecc(host->ctrl, p)) { + ecc->bytes = 3 * sectors; + mtd_set_ooblayout(mtd, &brcmnand_hamming_ooblayout_ops); + return 0; + } + + /* + * CONTROLLER_VERSION: + * < v5.0: ECC_REQ = ceil(BCH_T * 13/8) + * >= v5.0: ECC_REQ = ceil(BCH_T * 14/8) + * But we will just be conservative. + */ + ecc->bytes = DIV_ROUND_UP(ecc_level * 14, 8); + if (p->page_size == 512) + mtd_set_ooblayout(mtd, &brcmnand_bch_sp_ooblayout_ops); + else + mtd_set_ooblayout(mtd, &brcmnand_bch_lp_ooblayout_ops); + + if (ecc->bytes >= sas) { + dev_err(&host->pdev->dev, + "error: ECC too large for OOB (ECC bytes %d, spare sector %d)\n", + ecc->bytes, sas); + return -EINVAL; + } + + return 0; +} + +static void brcmnand_wp(struct mtd_info *mtd, int wp) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + + if ((ctrl->features & BRCMNAND_HAS_WP) && wp_on == 1) { + static int old_wp = -1; + int ret; + + if (old_wp != wp) { + dev_dbg(ctrl->dev, "WP %s\n", wp ? "on" : "off"); + old_wp = wp; + } + + /* + * make sure ctrl/flash ready before and after + * changing state of #WP pin + */ + ret = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY | + NAND_STATUS_READY, + NAND_CTRL_RDY | + NAND_STATUS_READY, 0); + if (ret) + return; + + brcmnand_set_wp(ctrl, wp); + nand_status_op(chip, NULL); + /* NAND_STATUS_WP 0x00 = protected, 0x80 = not protected */ + ret = bcmnand_ctrl_poll_status(ctrl, + NAND_CTRL_RDY | + NAND_STATUS_READY | + NAND_STATUS_WP, + NAND_CTRL_RDY | + NAND_STATUS_READY | + (wp ? 0 : NAND_STATUS_WP), 0); +#ifndef __UBOOT__ + if (ret) + dev_err_ratelimited(&host->pdev->dev, + "nand #WP expected %s\n", + wp ? "on" : "off"); +#else + if (ret) + dev_err(&host->pdev->dev, + "nand #WP expected %s\n", + wp ? "on" : "off"); +#endif /* __UBOOT__ */ + } +} + +/* Helper functions for reading and writing OOB registers */ +static inline u8 oob_reg_read(struct brcmnand_controller *ctrl, u32 offs) +{ + u16 offset0, offset10, reg_offs; + + offset0 = ctrl->reg_offsets[BRCMNAND_OOB_READ_BASE]; + offset10 = ctrl->reg_offsets[BRCMNAND_OOB_READ_10_BASE]; + + if (offs >= ctrl->max_oob) + return 0x77; + + if (offs >= 16 && offset10) + reg_offs = offset10 + ((offs - 0x10) & ~0x03); + else + reg_offs = offset0 + (offs & ~0x03); + + return nand_readreg(ctrl, reg_offs) >> (24 - ((offs & 0x03) << 3)); +} + +static inline void oob_reg_write(struct brcmnand_controller *ctrl, u32 offs, + u32 data) +{ + u16 offset0, offset10, reg_offs; + + offset0 = ctrl->reg_offsets[BRCMNAND_OOB_WRITE_BASE]; + offset10 = ctrl->reg_offsets[BRCMNAND_OOB_WRITE_10_BASE]; + + if (offs >= ctrl->max_oob) + return; + + if (offs >= 16 && offset10) + reg_offs = offset10 + ((offs - 0x10) & ~0x03); + else + reg_offs = offset0 + (offs & ~0x03); + + nand_writereg(ctrl, reg_offs, data); +} + +/* + * read_oob_from_regs - read data from OOB registers + * @ctrl: NAND controller + * @i: sub-page sector index + * @oob: buffer to read to + * @sas: spare area sector size (i.e., OOB size per FLASH_CACHE) + * @sector_1k: 1 for 1KiB sectors, 0 for 512B, other values are illegal + */ +static int read_oob_from_regs(struct brcmnand_controller *ctrl, int i, u8 *oob, + int sas, int sector_1k) +{ + int tbytes = sas << sector_1k; + int j; + + /* Adjust OOB values for 1K sector size */ + if (sector_1k && (i & 0x01)) + tbytes = max(0, tbytes - (int)ctrl->max_oob); + tbytes = min_t(int, tbytes, ctrl->max_oob); + + for (j = 0; j < tbytes; j++) + oob[j] = oob_reg_read(ctrl, j); + return tbytes; +} + +/* + * write_oob_to_regs - write data to OOB registers + * @i: sub-page sector index + * @oob: buffer to write from + * @sas: spare area sector size (i.e., OOB size per FLASH_CACHE) + * @sector_1k: 1 for 1KiB sectors, 0 for 512B, other values are illegal + */ +static int write_oob_to_regs(struct brcmnand_controller *ctrl, int i, + const u8 *oob, int sas, int sector_1k) +{ + int tbytes = sas << sector_1k; + int j; + + /* Adjust OOB values for 1K sector size */ + if (sector_1k && (i & 0x01)) + tbytes = max(0, tbytes - (int)ctrl->max_oob); + tbytes = min_t(int, tbytes, ctrl->max_oob); + + for (j = 0; j < tbytes; j += 4) + oob_reg_write(ctrl, j, + (oob[j + 0] << 24) | + (oob[j + 1] << 16) | + (oob[j + 2] << 8) | + (oob[j + 3] << 0)); + return tbytes; +} + +#ifndef __UBOOT__ +static irqreturn_t brcmnand_ctlrdy_irq(int irq, void *data) +{ + struct brcmnand_controller *ctrl = data; + + /* Discard all NAND_CTLRDY interrupts during DMA */ + if (ctrl->dma_pending) + return IRQ_HANDLED; + + complete(&ctrl->done); + return IRQ_HANDLED; +} + +/* Handle SoC-specific interrupt hardware */ +static irqreturn_t brcmnand_irq(int irq, void *data) +{ + struct brcmnand_controller *ctrl = data; + + if (ctrl->soc->ctlrdy_ack(ctrl->soc)) + return brcmnand_ctlrdy_irq(irq, data); + + return IRQ_NONE; +} + +static irqreturn_t brcmnand_dma_irq(int irq, void *data) +{ + struct brcmnand_controller *ctrl = data; + + complete(&ctrl->dma_done); + + return IRQ_HANDLED; +} +#endif /* __UBOOT__ */ + +static void brcmnand_send_cmd(struct brcmnand_host *host, int cmd) +{ + struct brcmnand_controller *ctrl = host->ctrl; + int ret; + + dev_dbg(ctrl->dev, "send native cmd %d addr_lo 0x%x\n", cmd, + brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS)); + BUG_ON(ctrl->cmd_pending != 0); + ctrl->cmd_pending = cmd; + + ret = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY, NAND_CTRL_RDY, 0); + WARN_ON(ret); + + mb(); /* flush previous writes */ + brcmnand_write_reg(ctrl, BRCMNAND_CMD_START, + cmd << brcmnand_cmd_shift(ctrl)); +} + +/*********************************************************************** + * NAND MTD API: read/program/erase + ***********************************************************************/ + +static void brcmnand_cmd_ctrl(struct mtd_info *mtd, int dat, + unsigned int ctrl) +{ + /* intentionally left blank */ +} + +static int brcmnand_waitfunc(struct mtd_info *mtd, struct nand_chip *this) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + +#ifndef __UBOOT__ + unsigned long timeo = msecs_to_jiffies(100); + + dev_dbg(ctrl->dev, "wait on native cmd %d\n", ctrl->cmd_pending); + if (ctrl->cmd_pending && + wait_for_completion_timeout(&ctrl->done, timeo) <= 0) { + u32 cmd = brcmnand_read_reg(ctrl, BRCMNAND_CMD_START) + >> brcmnand_cmd_shift(ctrl); + + dev_err_ratelimited(ctrl->dev, + "timeout waiting for command %#02x\n", cmd); + dev_err_ratelimited(ctrl->dev, "intfc status %08x\n", + brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS)); + } +#else + unsigned long timeo = 100; /* 100 msec */ + int ret; + + dev_dbg(ctrl->dev, "wait on native cmd %d\n", ctrl->cmd_pending); + + ret = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY, NAND_CTRL_RDY, timeo); + WARN_ON(ret); +#endif /* __UBOOT__ */ + + ctrl->cmd_pending = 0; + return brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) & + INTFC_FLASH_STATUS; +} + +enum { + LLOP_RE = BIT(16), + LLOP_WE = BIT(17), + LLOP_ALE = BIT(18), + LLOP_CLE = BIT(19), + LLOP_RETURN_IDLE = BIT(31), + + LLOP_DATA_MASK = GENMASK(15, 0), +}; + +static int brcmnand_low_level_op(struct brcmnand_host *host, + enum brcmnand_llop_type type, u32 data, + bool last_op) +{ + struct mtd_info *mtd = nand_to_mtd(&host->chip); + struct nand_chip *chip = &host->chip; + struct brcmnand_controller *ctrl = host->ctrl; + u32 tmp; + + tmp = data & LLOP_DATA_MASK; + switch (type) { + case LL_OP_CMD: + tmp |= LLOP_WE | LLOP_CLE; + break; + case LL_OP_ADDR: + /* WE | ALE */ + tmp |= LLOP_WE | LLOP_ALE; + break; + case LL_OP_WR: + /* WE */ + tmp |= LLOP_WE; + break; + case LL_OP_RD: + /* RE */ + tmp |= LLOP_RE; + break; + } + if (last_op) + /* RETURN_IDLE */ + tmp |= LLOP_RETURN_IDLE; + + dev_dbg(ctrl->dev, "ll_op cmd %#x\n", tmp); + + brcmnand_write_reg(ctrl, BRCMNAND_LL_OP, tmp); + (void)brcmnand_read_reg(ctrl, BRCMNAND_LL_OP); + + brcmnand_send_cmd(host, CMD_LOW_LEVEL_OP); + return brcmnand_waitfunc(mtd, chip); +} + +static void brcmnand_cmdfunc(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + u64 addr = (u64)page_addr << chip->page_shift; + int native_cmd = 0; + + if (command == NAND_CMD_READID || command == NAND_CMD_PARAM || + command == NAND_CMD_RNDOUT) + addr = (u64)column; + /* Avoid propagating a negative, don't-care address */ + else if (page_addr < 0) + addr = 0; + + dev_dbg(ctrl->dev, "cmd 0x%x addr 0x%llx\n", command, + (unsigned long long)addr); + + host->last_cmd = command; + host->last_byte = 0; + host->last_addr = addr; + + switch (command) { + case NAND_CMD_RESET: + native_cmd = CMD_FLASH_RESET; + break; + case NAND_CMD_STATUS: + native_cmd = CMD_STATUS_READ; + break; + case NAND_CMD_READID: + native_cmd = CMD_DEVICE_ID_READ; + break; + case NAND_CMD_READOOB: + native_cmd = CMD_SPARE_AREA_READ; + break; + case NAND_CMD_ERASE1: + native_cmd = CMD_BLOCK_ERASE; + brcmnand_wp(mtd, 0); + break; + case NAND_CMD_PARAM: + native_cmd = CMD_PARAMETER_READ; + break; + case NAND_CMD_SET_FEATURES: + case NAND_CMD_GET_FEATURES: + brcmnand_low_level_op(host, LL_OP_CMD, command, false); + brcmnand_low_level_op(host, LL_OP_ADDR, column, false); + break; + case NAND_CMD_RNDOUT: + native_cmd = CMD_PARAMETER_CHANGE_COL; + addr &= ~((u64)(FC_BYTES - 1)); + /* + * HW quirk: PARAMETER_CHANGE_COL requires SECTOR_SIZE_1K=0 + * NB: hwcfg.sector_size_1k may not be initialized yet + */ + if (brcmnand_get_sector_size_1k(host)) { + host->hwcfg.sector_size_1k = + brcmnand_get_sector_size_1k(host); + brcmnand_set_sector_size_1k(host, 0); + } + break; + } + + if (!native_cmd) + return; + + brcmnand_write_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS, + (host->cs << 16) | ((addr >> 32) & 0xffff)); + (void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS); + brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS, lower_32_bits(addr)); + (void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS); + + brcmnand_send_cmd(host, native_cmd); + brcmnand_waitfunc(mtd, chip); + + if (native_cmd == CMD_PARAMETER_READ || + native_cmd == CMD_PARAMETER_CHANGE_COL) { + /* Copy flash cache word-wise */ + u32 *flash_cache = (u32 *)ctrl->flash_cache; + int i; + + brcmnand_soc_data_bus_prepare(ctrl->soc, true); + + /* + * Must cache the FLASH_CACHE now, since changes in + * SECTOR_SIZE_1K may invalidate it + */ + for (i = 0; i < FC_WORDS; i++) { + u32 fc; + + fc = brcmnand_read_fc(ctrl, i); + + /* + * Flash cache is big endian for parameter pages, at + * least on STB SoCs + */ + if (ctrl->parameter_page_big_endian) + flash_cache[i] = be32_to_cpu(fc); + else + flash_cache[i] = le32_to_cpu(fc); + } + + brcmnand_soc_data_bus_unprepare(ctrl->soc, true); + + /* Cleanup from HW quirk: restore SECTOR_SIZE_1K */ + if (host->hwcfg.sector_size_1k) + brcmnand_set_sector_size_1k(host, + host->hwcfg.sector_size_1k); + } + + /* Re-enable protection is necessary only after erase */ + if (command == NAND_CMD_ERASE1) + brcmnand_wp(mtd, 1); +} + +static uint8_t brcmnand_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + uint8_t ret = 0; + int addr, offs; + + switch (host->last_cmd) { + case NAND_CMD_READID: + if (host->last_byte < 4) + ret = brcmnand_read_reg(ctrl, BRCMNAND_ID) >> + (24 - (host->last_byte << 3)); + else if (host->last_byte < 8) + ret = brcmnand_read_reg(ctrl, BRCMNAND_ID_EXT) >> + (56 - (host->last_byte << 3)); + break; + + case NAND_CMD_READOOB: + ret = oob_reg_read(ctrl, host->last_byte); + break; + + case NAND_CMD_STATUS: + ret = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) & + INTFC_FLASH_STATUS; + if (wp_on) /* hide WP status */ + ret |= NAND_STATUS_WP; + break; + + case NAND_CMD_PARAM: + case NAND_CMD_RNDOUT: + addr = host->last_addr + host->last_byte; + offs = addr & (FC_BYTES - 1); + + /* At FC_BYTES boundary, switch to next column */ + if (host->last_byte > 0 && offs == 0) + nand_change_read_column_op(chip, addr, NULL, 0, false); + + ret = ctrl->flash_cache[offs]; + break; + case NAND_CMD_GET_FEATURES: + if (host->last_byte >= ONFI_SUBFEATURE_PARAM_LEN) { + ret = 0; + } else { + bool last = host->last_byte == + ONFI_SUBFEATURE_PARAM_LEN - 1; + brcmnand_low_level_op(host, LL_OP_RD, 0, last); + ret = brcmnand_read_reg(ctrl, BRCMNAND_LL_RDATA) & 0xff; + } + } + + dev_dbg(ctrl->dev, "read byte = 0x%02x\n", ret); + host->last_byte++; + + return ret; +} + +static void brcmnand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + int i; + + for (i = 0; i < len; i++, buf++) + *buf = brcmnand_read_byte(mtd); +} + +static void brcmnand_write_buf(struct mtd_info *mtd, const uint8_t *buf, + int len) +{ + int i; + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + + switch (host->last_cmd) { + case NAND_CMD_SET_FEATURES: + for (i = 0; i < len; i++) + brcmnand_low_level_op(host, LL_OP_WR, buf[i], + (i + 1) == len); + break; + default: + BUG(); + break; + } +} + +/** + * Construct a FLASH_DMA descriptor as part of a linked list. You must know the + * following ahead of time: + * - Is this descriptor the beginning or end of a linked list? + * - What is the (DMA) address of the next descriptor in the linked list? + */ +#ifndef __UBOOT__ +static int brcmnand_fill_dma_desc(struct brcmnand_host *host, + struct brcm_nand_dma_desc *desc, u64 addr, + dma_addr_t buf, u32 len, u8 dma_cmd, + bool begin, bool end, + dma_addr_t next_desc) +{ + memset(desc, 0, sizeof(*desc)); + /* Descriptors are written in native byte order (wordwise) */ + desc->next_desc = lower_32_bits(next_desc); + desc->next_desc_ext = upper_32_bits(next_desc); + desc->cmd_irq = (dma_cmd << 24) | + (end ? (0x03 << 8) : 0) | /* IRQ | STOP */ + (!!begin) | ((!!end) << 1); /* head, tail */ +#ifdef CONFIG_CPU_BIG_ENDIAN + desc->cmd_irq |= 0x01 << 12; +#endif + desc->dram_addr = lower_32_bits(buf); + desc->dram_addr_ext = upper_32_bits(buf); + desc->tfr_len = len; + desc->total_len = len; + desc->flash_addr = lower_32_bits(addr); + desc->flash_addr_ext = upper_32_bits(addr); + desc->cs = host->cs; + desc->status_valid = 0x01; + return 0; +} + +/** + * Kick the FLASH_DMA engine, with a given DMA descriptor + */ +static void brcmnand_dma_run(struct brcmnand_host *host, dma_addr_t desc) +{ + struct brcmnand_controller *ctrl = host->ctrl; + unsigned long timeo = msecs_to_jiffies(100); + + flash_dma_writel(ctrl, FLASH_DMA_FIRST_DESC, lower_32_bits(desc)); + (void)flash_dma_readl(ctrl, FLASH_DMA_FIRST_DESC); + flash_dma_writel(ctrl, FLASH_DMA_FIRST_DESC_EXT, upper_32_bits(desc)); + (void)flash_dma_readl(ctrl, FLASH_DMA_FIRST_DESC_EXT); + + /* Start FLASH_DMA engine */ + ctrl->dma_pending = true; + mb(); /* flush previous writes */ + flash_dma_writel(ctrl, FLASH_DMA_CTRL, 0x03); /* wake | run */ + + if (wait_for_completion_timeout(&ctrl->dma_done, timeo) <= 0) { + dev_err(ctrl->dev, + "timeout waiting for DMA; status %#x, error status %#x\n", + flash_dma_readl(ctrl, FLASH_DMA_STATUS), + flash_dma_readl(ctrl, FLASH_DMA_ERROR_STATUS)); + } + ctrl->dma_pending = false; + flash_dma_writel(ctrl, FLASH_DMA_CTRL, 0); /* force stop */ +} + +static int brcmnand_dma_trans(struct brcmnand_host *host, u64 addr, u32 *buf, + u32 len, u8 dma_cmd) +{ + struct brcmnand_controller *ctrl = host->ctrl; + dma_addr_t buf_pa; + int dir = dma_cmd == CMD_PAGE_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + buf_pa = dma_map_single(ctrl->dev, buf, len, dir); + if (dma_mapping_error(ctrl->dev, buf_pa)) { + dev_err(ctrl->dev, "unable to map buffer for DMA\n"); + return -ENOMEM; + } + + brcmnand_fill_dma_desc(host, ctrl->dma_desc, addr, buf_pa, len, + dma_cmd, true, true, 0); + + brcmnand_dma_run(host, ctrl->dma_pa); + + dma_unmap_single(ctrl->dev, buf_pa, len, dir); + + if (ctrl->dma_desc->status_valid & FLASH_DMA_ECC_ERROR) + return -EBADMSG; + else if (ctrl->dma_desc->status_valid & FLASH_DMA_CORR_ERROR) + return -EUCLEAN; + + return 0; +} +#endif /* __UBOOT__ */ + +/* + * Assumes proper CS is already set + */ +static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip, + u64 addr, unsigned int trans, u32 *buf, + u8 *oob, u64 *err_addr) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + int i, j, ret = 0; + + /* Clear error addresses */ + brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_ADDR, 0); + brcmnand_write_reg(ctrl, BRCMNAND_CORR_ADDR, 0); + brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_EXT_ADDR, 0); + brcmnand_write_reg(ctrl, BRCMNAND_CORR_EXT_ADDR, 0); + + brcmnand_write_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS, + (host->cs << 16) | ((addr >> 32) & 0xffff)); + (void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS); + + for (i = 0; i < trans; i++, addr += FC_BYTES) { + brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS, + lower_32_bits(addr)); + (void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS); + /* SPARE_AREA_READ does not use ECC, so just use PAGE_READ */ + brcmnand_send_cmd(host, CMD_PAGE_READ); + brcmnand_waitfunc(mtd, chip); + + if (likely(buf)) { + brcmnand_soc_data_bus_prepare(ctrl->soc, false); + + for (j = 0; j < FC_WORDS; j++, buf++) + *buf = brcmnand_read_fc(ctrl, j); + + brcmnand_soc_data_bus_unprepare(ctrl->soc, false); + } + + if (oob) + oob += read_oob_from_regs(ctrl, i, oob, + mtd->oobsize / trans, + host->hwcfg.sector_size_1k); + + if (!ret) { + *err_addr = brcmnand_read_reg(ctrl, + BRCMNAND_UNCORR_ADDR) | + ((u64)(brcmnand_read_reg(ctrl, + BRCMNAND_UNCORR_EXT_ADDR) + & 0xffff) << 32); + if (*err_addr) + ret = -EBADMSG; + } + + if (!ret) { + *err_addr = brcmnand_read_reg(ctrl, + BRCMNAND_CORR_ADDR) | + ((u64)(brcmnand_read_reg(ctrl, + BRCMNAND_CORR_EXT_ADDR) + & 0xffff) << 32); + if (*err_addr) + ret = -EUCLEAN; + } + } + + return ret; +} + +/* + * Check a page to see if it is erased (w/ bitflips) after an uncorrectable ECC + * error + * + * Because the HW ECC signals an ECC error if an erase paged has even a single + * bitflip, we must check each ECC error to see if it is actually an erased + * page with bitflips, not a truly corrupted page. + * + * On a real error, return a negative error code (-EBADMSG for ECC error), and + * buf will contain raw data. + * Otherwise, buf gets filled with 0xffs and return the maximum number of + * bitflips-per-ECC-sector to the caller. + * + */ +static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd, + struct nand_chip *chip, void *buf, u64 addr) +{ + int i, sas; + void *oob = chip->oob_poi; + int bitflips = 0; + int page = addr >> chip->page_shift; + int ret; + + if (!buf) { +#ifndef __UBOOT__ + buf = chip->data_buf; +#else + buf = chip->buffers->databuf; +#endif + /* Invalidate page cache */ + chip->pagebuf = -1; + } + + sas = mtd->oobsize / chip->ecc.steps; + + /* read without ecc for verification */ + ret = chip->ecc.read_page_raw(mtd, chip, buf, true, page); + if (ret) + return ret; + + for (i = 0; i < chip->ecc.steps; i++, oob += sas) { + ret = nand_check_erased_ecc_chunk(buf, chip->ecc.size, + oob, sas, NULL, 0, + chip->ecc.strength); + if (ret < 0) + return ret; + + bitflips = max(bitflips, ret); + } + + return bitflips; +} + +static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip, + u64 addr, unsigned int trans, u32 *buf, u8 *oob) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + u64 err_addr = 0; + int err; + bool retry = true; + + dev_dbg(ctrl->dev, "read %llx -> %p\n", (unsigned long long)addr, buf); + +try_dmaread: + brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_COUNT, 0); + +#ifndef __UBOOT__ + if (has_flash_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) { + err = brcmnand_dma_trans(host, addr, buf, trans * FC_BYTES, + CMD_PAGE_READ); + if (err) { + if (mtd_is_bitflip_or_eccerr(err)) + err_addr = addr; + else + return -EIO; + } + } else { + if (oob) + memset(oob, 0x99, mtd->oobsize); + + err = brcmnand_read_by_pio(mtd, chip, addr, trans, buf, + oob, &err_addr); + } +#else + if (oob) + memset(oob, 0x99, mtd->oobsize); + + err = brcmnand_read_by_pio(mtd, chip, addr, trans, buf, + oob, &err_addr); +#endif /* __UBOOT__ */ + + if (mtd_is_eccerr(err)) { + /* + * On controller version and 7.0, 7.1 , DMA read after a + * prior PIO read that reported uncorrectable error, + * the DMA engine captures this error following DMA read + * cleared only on subsequent DMA read, so just retry once + * to clear a possible false error reported for current DMA + * read + */ + if ((ctrl->nand_version == 0x0700) || + (ctrl->nand_version == 0x0701)) { + if (retry) { + retry = false; + goto try_dmaread; + } + } + + /* + * Controller version 7.2 has hw encoder to detect erased page + * bitflips, apply sw verification for older controllers only + */ + if (ctrl->nand_version < 0x0702) { + err = brcmstb_nand_verify_erased_page(mtd, chip, buf, + addr); + /* erased page bitflips corrected */ + if (err >= 0) + return err; + } + + dev_dbg(ctrl->dev, "uncorrectable error at 0x%llx\n", + (unsigned long long)err_addr); + mtd->ecc_stats.failed++; + /* NAND layer expects zero on ECC errors */ + return 0; + } + + if (mtd_is_bitflip(err)) { + unsigned int corrected = brcmnand_count_corrected(ctrl); + + dev_dbg(ctrl->dev, "corrected error at 0x%llx\n", + (unsigned long long)err_addr); + mtd->ecc_stats.corrected += corrected; + /* Always exceed the software-imposed threshold */ + return max(mtd->bitflip_threshold, corrected); + } + + return 0; +} + +static int brcmnand_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL; + + nand_read_page_op(chip, page, 0, NULL, 0); + + return brcmnand_read(mtd, chip, host->last_addr, + mtd->writesize >> FC_SHIFT, (u32 *)buf, oob); +} + +static int brcmnand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL; + int ret; + + nand_read_page_op(chip, page, 0, NULL, 0); + + brcmnand_set_ecc_enabled(host, 0); + ret = brcmnand_read(mtd, chip, host->last_addr, + mtd->writesize >> FC_SHIFT, (u32 *)buf, oob); + brcmnand_set_ecc_enabled(host, 1); + return ret; +} + +static int brcmnand_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + return brcmnand_read(mtd, chip, (u64)page << chip->page_shift, + mtd->writesize >> FC_SHIFT, + NULL, (u8 *)chip->oob_poi); +} + +static int brcmnand_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + + brcmnand_set_ecc_enabled(host, 0); + brcmnand_read(mtd, chip, (u64)page << chip->page_shift, + mtd->writesize >> FC_SHIFT, + NULL, (u8 *)chip->oob_poi); + brcmnand_set_ecc_enabled(host, 1); + return 0; +} + +static int brcmnand_write(struct mtd_info *mtd, struct nand_chip *chip, + u64 addr, const u32 *buf, u8 *oob) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + unsigned int i, j, trans = mtd->writesize >> FC_SHIFT; + int status, ret = 0; + + dev_dbg(ctrl->dev, "write %llx <- %p\n", (unsigned long long)addr, buf); + + if (unlikely((unsigned long)buf & 0x03)) { + dev_warn(ctrl->dev, "unaligned buffer: %p\n", buf); + buf = (u32 *)((unsigned long)buf & ~0x03); + } + + brcmnand_wp(mtd, 0); + + for (i = 0; i < ctrl->max_oob; i += 4) + oob_reg_write(ctrl, i, 0xffffffff); + +#ifndef __UBOOT__ + if (has_flash_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) { + if (brcmnand_dma_trans(host, addr, (u32 *)buf, + mtd->writesize, CMD_PROGRAM_PAGE)) + ret = -EIO; + goto out; + } +#endif /* __UBOOT__ */ + + brcmnand_write_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS, + (host->cs << 16) | ((addr >> 32) & 0xffff)); + (void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS); + + for (i = 0; i < trans; i++, addr += FC_BYTES) { + /* full address MUST be set before populating FC */ + brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS, + lower_32_bits(addr)); + (void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS); + + if (buf) { + brcmnand_soc_data_bus_prepare(ctrl->soc, false); + + for (j = 0; j < FC_WORDS; j++, buf++) + brcmnand_write_fc(ctrl, j, *buf); + + brcmnand_soc_data_bus_unprepare(ctrl->soc, false); + } else if (oob) { + for (j = 0; j < FC_WORDS; j++) + brcmnand_write_fc(ctrl, j, 0xffffffff); + } + + if (oob) { + oob += write_oob_to_regs(ctrl, i, oob, + mtd->oobsize / trans, + host->hwcfg.sector_size_1k); + } + + /* we cannot use SPARE_AREA_PROGRAM when PARTIAL_PAGE_EN=0 */ + brcmnand_send_cmd(host, CMD_PROGRAM_PAGE); + status = brcmnand_waitfunc(mtd, chip); + + if (status & NAND_STATUS_FAIL) { + dev_info(ctrl->dev, "program failed at %llx\n", + (unsigned long long)addr); + ret = -EIO; + goto out; + } + } +out: + brcmnand_wp(mtd, 1); + return ret; +} + +static int brcmnand_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required, int page) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + void *oob = oob_required ? chip->oob_poi : NULL; + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob); + + return nand_prog_page_end_op(chip); +} + +static int brcmnand_write_page_raw(struct mtd_info *mtd, + struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + void *oob = oob_required ? chip->oob_poi : NULL; + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + brcmnand_set_ecc_enabled(host, 0); + brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob); + brcmnand_set_ecc_enabled(host, 1); + + return nand_prog_page_end_op(chip); +} + +static int brcmnand_write_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + return brcmnand_write(mtd, chip, (u64)page << chip->page_shift, + NULL, chip->oob_poi); +} + +static int brcmnand_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + int ret; + + brcmnand_set_ecc_enabled(host, 0); + ret = brcmnand_write(mtd, chip, (u64)page << chip->page_shift, NULL, + (u8 *)chip->oob_poi); + brcmnand_set_ecc_enabled(host, 1); + + return ret; +} + +/*********************************************************************** + * Per-CS setup (1 NAND device) + ***********************************************************************/ + +static int brcmnand_set_cfg(struct brcmnand_host *host, + struct brcmnand_cfg *cfg) +{ + struct brcmnand_controller *ctrl = host->ctrl; + struct nand_chip *chip = &host->chip; + u16 cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG); + u16 cfg_ext_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_CFG_EXT); + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + u8 block_size = 0, page_size = 0, device_size = 0; + u32 tmp; + + if (ctrl->block_sizes) { + int i, found; + + for (i = 0, found = 0; ctrl->block_sizes[i]; i++) + if (ctrl->block_sizes[i] * 1024 == cfg->block_size) { + block_size = i; + found = 1; + } + if (!found) { + dev_warn(ctrl->dev, "invalid block size %u\n", + cfg->block_size); + return -EINVAL; + } + } else { + block_size = ffs(cfg->block_size) - ffs(BRCMNAND_MIN_BLOCKSIZE); + } + + if (cfg->block_size < BRCMNAND_MIN_BLOCKSIZE || (ctrl->max_block_size && + cfg->block_size > ctrl->max_block_size)) { + dev_warn(ctrl->dev, "invalid block size %u\n", + cfg->block_size); + block_size = 0; + } + + if (ctrl->page_sizes) { + int i, found; + + for (i = 0, found = 0; ctrl->page_sizes[i]; i++) + if (ctrl->page_sizes[i] == cfg->page_size) { + page_size = i; + found = 1; + } + if (!found) { + dev_warn(ctrl->dev, "invalid page size %u\n", + cfg->page_size); + return -EINVAL; + } + } else { + page_size = ffs(cfg->page_size) - ffs(BRCMNAND_MIN_PAGESIZE); + } + + if (cfg->page_size < BRCMNAND_MIN_PAGESIZE || (ctrl->max_page_size && + cfg->page_size > ctrl->max_page_size)) { + dev_warn(ctrl->dev, "invalid page size %u\n", cfg->page_size); + return -EINVAL; + } + + if (fls64(cfg->device_size) < fls64(BRCMNAND_MIN_DEVSIZE)) { + dev_warn(ctrl->dev, "invalid device size 0x%llx\n", + (unsigned long long)cfg->device_size); + return -EINVAL; + } + device_size = fls64(cfg->device_size) - fls64(BRCMNAND_MIN_DEVSIZE); + + tmp = (cfg->blk_adr_bytes << CFG_BLK_ADR_BYTES_SHIFT) | + (cfg->col_adr_bytes << CFG_COL_ADR_BYTES_SHIFT) | + (cfg->ful_adr_bytes << CFG_FUL_ADR_BYTES_SHIFT) | + (!!(cfg->device_width == 16) << CFG_BUS_WIDTH_SHIFT) | + (device_size << CFG_DEVICE_SIZE_SHIFT); + if (cfg_offs == cfg_ext_offs) { + tmp |= (page_size << CFG_PAGE_SIZE_SHIFT) | + (block_size << CFG_BLK_SIZE_SHIFT); + nand_writereg(ctrl, cfg_offs, tmp); + } else { + nand_writereg(ctrl, cfg_offs, tmp); + tmp = (page_size << CFG_EXT_PAGE_SIZE_SHIFT) | + (block_size << CFG_EXT_BLK_SIZE_SHIFT); + nand_writereg(ctrl, cfg_ext_offs, tmp); + } + + tmp = nand_readreg(ctrl, acc_control_offs); + tmp &= ~brcmnand_ecc_level_mask(ctrl); + tmp |= cfg->ecc_level << NAND_ACC_CONTROL_ECC_SHIFT; + tmp &= ~brcmnand_spare_area_mask(ctrl); + tmp |= cfg->spare_area_size; + nand_writereg(ctrl, acc_control_offs, tmp); + + brcmnand_set_sector_size_1k(host, cfg->sector_size_1k); + + /* threshold = ceil(BCH-level * 0.75) */ + brcmnand_wr_corr_thresh(host, DIV_ROUND_UP(chip->ecc.strength * 3, 4)); + + return 0; +} + +static void brcmnand_print_cfg(struct brcmnand_host *host, + char *buf, struct brcmnand_cfg *cfg) +{ + buf += sprintf(buf, + "%lluMiB total, %uKiB blocks, %u%s pages, %uB OOB, %u-bit", + (unsigned long long)cfg->device_size >> 20, + cfg->block_size >> 10, + cfg->page_size >= 1024 ? cfg->page_size >> 10 : cfg->page_size, + cfg->page_size >= 1024 ? "KiB" : "B", + cfg->spare_area_size, cfg->device_width); + + /* Account for Hamming ECC and for BCH 512B vs 1KiB sectors */ + if (is_hamming_ecc(host->ctrl, cfg)) + sprintf(buf, ", Hamming ECC"); + else if (cfg->sector_size_1k) + sprintf(buf, ", BCH-%u (1KiB sector)", cfg->ecc_level << 1); + else + sprintf(buf, ", BCH-%u", cfg->ecc_level); +} + +/* + * Minimum number of bytes to address a page. Calculated as: + * roundup(log2(size / page-size) / 8) + * + * NB: the following does not "round up" for non-power-of-2 'size'; but this is + * OK because many other things will break if 'size' is irregular... + */ +static inline int get_blk_adr_bytes(u64 size, u32 writesize) +{ + return ALIGN(ilog2(size) - ilog2(writesize), 8) >> 3; +} + +static int brcmnand_setup_dev(struct brcmnand_host *host) +{ + struct mtd_info *mtd = nand_to_mtd(&host->chip); + struct nand_chip *chip = &host->chip; + struct brcmnand_controller *ctrl = host->ctrl; + struct brcmnand_cfg *cfg = &host->hwcfg; + char msg[128]; + u32 offs, tmp, oob_sector; + int ret; + + memset(cfg, 0, sizeof(*cfg)); + +#ifndef __UBOOT__ + ret = of_property_read_u32(nand_get_flash_node(chip), + "brcm,nand-oob-sector-size", + &oob_sector); +#else + ret = ofnode_read_u32(nand_get_flash_node(chip), + "brcm,nand-oob-sector-size", + &oob_sector); +#endif /* __UBOOT__ */ + if (ret) { + /* Use detected size */ + cfg->spare_area_size = mtd->oobsize / + (mtd->writesize >> FC_SHIFT); + } else { + cfg->spare_area_size = oob_sector; + } + if (cfg->spare_area_size > ctrl->max_oob) + cfg->spare_area_size = ctrl->max_oob; + /* + * Set oobsize to be consistent with controller's spare_area_size, as + * the rest is inaccessible. + */ + mtd->oobsize = cfg->spare_area_size * (mtd->writesize >> FC_SHIFT); + + cfg->device_size = mtd->size; + cfg->block_size = mtd->erasesize; + cfg->page_size = mtd->writesize; + cfg->device_width = (chip->options & NAND_BUSWIDTH_16) ? 16 : 8; + cfg->col_adr_bytes = 2; + cfg->blk_adr_bytes = get_blk_adr_bytes(mtd->size, mtd->writesize); + + if (chip->ecc.mode != NAND_ECC_HW) { + dev_err(ctrl->dev, "only HW ECC supported; selected: %d\n", + chip->ecc.mode); + return -EINVAL; + } + + if (chip->ecc.algo == NAND_ECC_UNKNOWN) { + if (chip->ecc.strength == 1 && chip->ecc.size == 512) + /* Default to Hamming for 1-bit ECC, if unspecified */ + chip->ecc.algo = NAND_ECC_HAMMING; + else + /* Otherwise, BCH */ + chip->ecc.algo = NAND_ECC_BCH; + } + + if (chip->ecc.algo == NAND_ECC_HAMMING && (chip->ecc.strength != 1 || + chip->ecc.size != 512)) { + dev_err(ctrl->dev, "invalid Hamming params: %d bits per %d bytes\n", + chip->ecc.strength, chip->ecc.size); + return -EINVAL; + } + + switch (chip->ecc.size) { + case 512: + if (chip->ecc.algo == NAND_ECC_HAMMING) + cfg->ecc_level = 15; + else + cfg->ecc_level = chip->ecc.strength; + cfg->sector_size_1k = 0; + break; + case 1024: + if (!(ctrl->features & BRCMNAND_HAS_1K_SECTORS)) { + dev_err(ctrl->dev, "1KB sectors not supported\n"); + return -EINVAL; + } + if (chip->ecc.strength & 0x1) { + dev_err(ctrl->dev, + "odd ECC not supported with 1KB sectors\n"); + return -EINVAL; + } + + cfg->ecc_level = chip->ecc.strength >> 1; + cfg->sector_size_1k = 1; + break; + default: + dev_err(ctrl->dev, "unsupported ECC size: %d\n", + chip->ecc.size); + return -EINVAL; + } + + cfg->ful_adr_bytes = cfg->blk_adr_bytes; + if (mtd->writesize > 512) + cfg->ful_adr_bytes += cfg->col_adr_bytes; + else + cfg->ful_adr_bytes += 1; + + ret = brcmnand_set_cfg(host, cfg); + if (ret) + return ret; + + brcmnand_set_ecc_enabled(host, 1); + + brcmnand_print_cfg(host, msg, cfg); + dev_info(ctrl->dev, "detected %s\n", msg); + + /* Configure ACC_CONTROL */ + offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_ACC_CONTROL); + tmp = nand_readreg(ctrl, offs); + tmp &= ~ACC_CONTROL_PARTIAL_PAGE; + tmp &= ~ACC_CONTROL_RD_ERASED; + + /* We need to turn on Read from erased paged protected by ECC */ + if (ctrl->nand_version >= 0x0702) + tmp |= ACC_CONTROL_RD_ERASED; + tmp &= ~ACC_CONTROL_FAST_PGM_RDIN; + if (ctrl->features & BRCMNAND_HAS_PREFETCH) + tmp &= ~ACC_CONTROL_PREFETCH; + + nand_writereg(ctrl, offs, tmp); + + return 0; +} + +#ifndef __UBOOT__ +static int brcmnand_init_cs(struct brcmnand_host *host, struct device_node *dn) +#else +static int brcmnand_init_cs(struct brcmnand_host *host, ofnode dn) +#endif +{ + struct brcmnand_controller *ctrl = host->ctrl; +#ifndef __UBOOT__ + struct platform_device *pdev = host->pdev; +#else + struct udevice *pdev = host->pdev; +#endif /* __UBOOT__ */ + struct mtd_info *mtd; + struct nand_chip *chip; + int ret; + u16 cfg_offs; + +#ifndef __UBOOT__ + ret = of_property_read_u32(dn, "reg", &host->cs); +#else + ret = ofnode_read_s32(dn, "reg", &host->cs); +#endif + if (ret) { + dev_err(&pdev->dev, "can't get chip-select\n"); + return -ENXIO; + } + + mtd = nand_to_mtd(&host->chip); + chip = &host->chip; + + nand_set_flash_node(chip, dn); + nand_set_controller_data(chip, host); +#ifndef __UBOOT__ + mtd->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "brcmnand.%d", + host->cs); +#else + mtd->name = devm_kasprintf(pdev, GFP_KERNEL, "brcmnand.%d", + host->cs); +#endif /* __UBOOT__ */ + if (!mtd->name) + return -ENOMEM; + + mtd->owner = THIS_MODULE; +#ifndef __UBOOT__ + mtd->dev.parent = &pdev->dev; +#else + mtd->dev->parent = pdev; +#endif /* __UBOOT__ */ + + chip->IO_ADDR_R = (void __iomem *)0xdeadbeef; + chip->IO_ADDR_W = (void __iomem *)0xdeadbeef; + + chip->cmd_ctrl = brcmnand_cmd_ctrl; + chip->cmdfunc = brcmnand_cmdfunc; + chip->waitfunc = brcmnand_waitfunc; + chip->read_byte = brcmnand_read_byte; + chip->read_buf = brcmnand_read_buf; + chip->write_buf = brcmnand_write_buf; + + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.read_page = brcmnand_read_page; + chip->ecc.write_page = brcmnand_write_page; + chip->ecc.read_page_raw = brcmnand_read_page_raw; + chip->ecc.write_page_raw = brcmnand_write_page_raw; + chip->ecc.write_oob_raw = brcmnand_write_oob_raw; + chip->ecc.read_oob_raw = brcmnand_read_oob_raw; + chip->ecc.read_oob = brcmnand_read_oob; + chip->ecc.write_oob = brcmnand_write_oob; + + chip->controller = &ctrl->controller; + + /* + * The bootloader might have configured 16bit mode but + * NAND READID command only works in 8bit mode. We force + * 8bit mode here to ensure that NAND READID commands works. + */ + cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG); + nand_writereg(ctrl, cfg_offs, + nand_readreg(ctrl, cfg_offs) & ~CFG_BUS_WIDTH); + + ret = nand_scan_ident(mtd, 1, NULL); + if (ret) + return ret; + + chip->options |= NAND_NO_SUBPAGE_WRITE; + /* + * Avoid (for instance) kmap()'d buffers from JFFS2, which we can't DMA + * to/from, and have nand_base pass us a bounce buffer instead, as + * needed. + */ + chip->options |= NAND_USE_BOUNCE_BUFFER; + + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + if (brcmnand_setup_dev(host)) + return -ENXIO; + + chip->ecc.size = host->hwcfg.sector_size_1k ? 1024 : 512; + /* only use our internal HW threshold */ + mtd->bitflip_threshold = 1; + + ret = brcmstb_choose_ecc_layout(host); + if (ret) + return ret; + + ret = nand_scan_tail(mtd); + if (ret) + return ret; + +#ifndef __UBOOT__ + ret = mtd_device_register(mtd, NULL, 0); + if (ret) + nand_cleanup(chip); +#else + ret = nand_register(0, mtd); +#endif /* __UBOOT__ */ + + return ret; +} + +#ifndef __UBOOT__ +static void brcmnand_save_restore_cs_config(struct brcmnand_host *host, + int restore) +{ + struct brcmnand_controller *ctrl = host->ctrl; + u16 cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG); + u16 cfg_ext_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_CFG_EXT); + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + u16 t1_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_TIMING1); + u16 t2_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_TIMING2); + + if (restore) { + nand_writereg(ctrl, cfg_offs, host->hwcfg.config); + if (cfg_offs != cfg_ext_offs) + nand_writereg(ctrl, cfg_ext_offs, + host->hwcfg.config_ext); + nand_writereg(ctrl, acc_control_offs, host->hwcfg.acc_control); + nand_writereg(ctrl, t1_offs, host->hwcfg.timing_1); + nand_writereg(ctrl, t2_offs, host->hwcfg.timing_2); + } else { + host->hwcfg.config = nand_readreg(ctrl, cfg_offs); + if (cfg_offs != cfg_ext_offs) + host->hwcfg.config_ext = + nand_readreg(ctrl, cfg_ext_offs); + host->hwcfg.acc_control = nand_readreg(ctrl, acc_control_offs); + host->hwcfg.timing_1 = nand_readreg(ctrl, t1_offs); + host->hwcfg.timing_2 = nand_readreg(ctrl, t2_offs); + } +} + +static int brcmnand_suspend(struct device *dev) +{ + struct brcmnand_controller *ctrl = dev_get_drvdata(dev); + struct brcmnand_host *host; + + list_for_each_entry(host, &ctrl->host_list, node) + brcmnand_save_restore_cs_config(host, 0); + + ctrl->nand_cs_nand_select = brcmnand_read_reg(ctrl, BRCMNAND_CS_SELECT); + ctrl->nand_cs_nand_xor = brcmnand_read_reg(ctrl, BRCMNAND_CS_XOR); + ctrl->corr_stat_threshold = + brcmnand_read_reg(ctrl, BRCMNAND_CORR_THRESHOLD); + + if (has_flash_dma(ctrl)) + ctrl->flash_dma_mode = flash_dma_readl(ctrl, FLASH_DMA_MODE); + + return 0; +} + +static int brcmnand_resume(struct device *dev) +{ + struct brcmnand_controller *ctrl = dev_get_drvdata(dev); + struct brcmnand_host *host; + + if (has_flash_dma(ctrl)) { + flash_dma_writel(ctrl, FLASH_DMA_MODE, ctrl->flash_dma_mode); + flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0); + } + + brcmnand_write_reg(ctrl, BRCMNAND_CS_SELECT, ctrl->nand_cs_nand_select); + brcmnand_write_reg(ctrl, BRCMNAND_CS_XOR, ctrl->nand_cs_nand_xor); + brcmnand_write_reg(ctrl, BRCMNAND_CORR_THRESHOLD, + ctrl->corr_stat_threshold); + if (ctrl->soc) { + /* Clear/re-enable interrupt */ + ctrl->soc->ctlrdy_ack(ctrl->soc); + ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true); + } + + list_for_each_entry(host, &ctrl->host_list, node) { + struct nand_chip *chip = &host->chip; + + brcmnand_save_restore_cs_config(host, 1); + + /* Reset the chip, required by some chips after power-up */ + nand_reset_op(chip); + } + + return 0; +} + +const struct dev_pm_ops brcmnand_pm_ops = { + .suspend = brcmnand_suspend, + .resume = brcmnand_resume, +}; +EXPORT_SYMBOL_GPL(brcmnand_pm_ops); + +static const struct of_device_id brcmnand_of_match[] = { + { .compatible = "brcm,brcmnand-v4.0" }, + { .compatible = "brcm,brcmnand-v5.0" }, + { .compatible = "brcm,brcmnand-v6.0" }, + { .compatible = "brcm,brcmnand-v6.1" }, + { .compatible = "brcm,brcmnand-v6.2" }, + { .compatible = "brcm,brcmnand-v7.0" }, + { .compatible = "brcm,brcmnand-v7.1" }, + { .compatible = "brcm,brcmnand-v7.2" }, + {}, +}; +MODULE_DEVICE_TABLE(of, brcmnand_of_match); +#endif /* __UBOOT__ */ + +/*********************************************************************** + * Platform driver setup (per controller) + ***********************************************************************/ + +#ifndef __UBOOT__ +int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc) +#else +int brcmnand_probe(struct udevice *dev, struct brcmnand_soc *soc) +#endif /* __UBOOT__ */ +{ +#ifndef __UBOOT__ + struct device *dev = &pdev->dev; + struct device_node *dn = dev->of_node, *child; +#else + ofnode child; + struct udevice *pdev = dev; +#endif /* __UBOOT__ */ + struct brcmnand_controller *ctrl; +#ifndef __UBOOT__ + struct resource *res; +#else + struct resource res; +#endif /* __UBOOT__ */ + int ret; + +#ifndef __UBOOT__ + /* We only support device-tree instantiation */ + if (!dn) + return -ENODEV; + + if (!of_match_node(brcmnand_of_match, dn)) + return -ENODEV; +#endif /* __UBOOT__ */ + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + +#ifndef __UBOOT__ + dev_set_drvdata(dev, ctrl); +#else + /* + * in u-boot, the data for the driver is allocated before probing + * so to keep the reference to ctrl, we store it in the variable soc + */ + soc->ctrl = ctrl; +#endif /* __UBOOT__ */ + ctrl->dev = dev; + + init_completion(&ctrl->done); + init_completion(&ctrl->dma_done); + nand_hw_control_init(&ctrl->controller); + INIT_LIST_HEAD(&ctrl->host_list); + + /* Is parameter page in big endian ? */ + ctrl->parameter_page_big_endian = + dev_read_u32_default(dev, "parameter-page-big-endian", 1); + + /* NAND register range */ +#ifndef __UBOOT__ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ctrl->nand_base = devm_ioremap_resource(dev, res); +#else + dev_read_resource(pdev, 0, &res); + ctrl->nand_base = devm_ioremap(pdev, res.start, resource_size(&res)); +#endif + if (IS_ERR(ctrl->nand_base)) + return PTR_ERR(ctrl->nand_base); + + /* Enable clock before using NAND registers */ + ctrl->clk = devm_clk_get(dev, "nand"); + if (!IS_ERR(ctrl->clk)) { + ret = clk_prepare_enable(ctrl->clk); + if (ret) + return ret; + } else { + ret = PTR_ERR(ctrl->clk); + if (ret == -EPROBE_DEFER) + return ret; + + ctrl->clk = NULL; + } + + /* Initialize NAND revision */ + ret = brcmnand_revision_init(ctrl); + if (ret) + goto err; + + /* + * Most chips have this cache at a fixed offset within 'nand' block. + * Some must specify this region separately. + */ +#ifndef __UBOOT__ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand-cache"); + if (res) { + ctrl->nand_fc = devm_ioremap_resource(dev, res); + if (IS_ERR(ctrl->nand_fc)) { + ret = PTR_ERR(ctrl->nand_fc); + goto err; + } + } else { + ctrl->nand_fc = ctrl->nand_base + + ctrl->reg_offsets[BRCMNAND_FC_BASE]; + } +#else + if (!dev_read_resource_byname(pdev, "nand-cache", &res)) { + ctrl->nand_fc = devm_ioremap(dev, res.start, + resource_size(&res)); + if (IS_ERR(ctrl->nand_fc)) { + ret = PTR_ERR(ctrl->nand_fc); + goto err; + } + } else { + ctrl->nand_fc = ctrl->nand_base + + ctrl->reg_offsets[BRCMNAND_FC_BASE]; + } +#endif + +#ifndef __UBOOT__ + /* FLASH_DMA */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "flash-dma"); + if (res) { + ctrl->flash_dma_base = devm_ioremap_resource(dev, res); + if (IS_ERR(ctrl->flash_dma_base)) { + ret = PTR_ERR(ctrl->flash_dma_base); + goto err; + } + + flash_dma_writel(ctrl, FLASH_DMA_MODE, 1); /* linked-list */ + flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0); + + /* Allocate descriptor(s) */ + ctrl->dma_desc = dmam_alloc_coherent(dev, + sizeof(*ctrl->dma_desc), + &ctrl->dma_pa, GFP_KERNEL); + if (!ctrl->dma_desc) { + ret = -ENOMEM; + goto err; + } + + ctrl->dma_irq = platform_get_irq(pdev, 1); + if ((int)ctrl->dma_irq < 0) { + dev_err(dev, "missing FLASH_DMA IRQ\n"); + ret = -ENODEV; + goto err; + } + + ret = devm_request_irq(dev, ctrl->dma_irq, + brcmnand_dma_irq, 0, DRV_NAME, + ctrl); + if (ret < 0) { + dev_err(dev, "can't allocate IRQ %d: error %d\n", + ctrl->dma_irq, ret); + goto err; + } + + dev_info(dev, "enabling FLASH_DMA\n"); + } +#endif /* __UBOOT__ */ + + /* Disable automatic device ID config, direct addressing */ + brcmnand_rmw_reg(ctrl, BRCMNAND_CS_SELECT, + CS_SELECT_AUTO_DEVICE_ID_CFG | 0xff, 0, 0); + /* Disable XOR addressing */ + brcmnand_rmw_reg(ctrl, BRCMNAND_CS_XOR, 0xff, 0, 0); + + /* Read the write-protect configuration in the device tree */ + wp_on = dev_read_u32_default(dev, "write-protect", wp_on); + + if (ctrl->features & BRCMNAND_HAS_WP) { + /* Permanently disable write protection */ + if (wp_on == 2) + brcmnand_set_wp(ctrl, false); + } else { + wp_on = 0; + } + +#ifndef __UBOOT__ + /* IRQ */ + ctrl->irq = platform_get_irq(pdev, 0); + if ((int)ctrl->irq < 0) { + dev_err(dev, "no IRQ defined\n"); + ret = -ENODEV; + goto err; + } + + /* + * Some SoCs integrate this controller (e.g., its interrupt bits) in + * interesting ways + */ + if (soc) { + ctrl->soc = soc; + + ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0, + DRV_NAME, ctrl); + + /* Enable interrupt */ + ctrl->soc->ctlrdy_ack(ctrl->soc); + ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true); + } else { + /* Use standard interrupt infrastructure */ + ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0, + DRV_NAME, ctrl); + } + if (ret < 0) { + dev_err(dev, "can't allocate IRQ %d: error %d\n", + ctrl->irq, ret); + goto err; + } +#endif /* __UBOOT__ */ + +#ifndef __UBOOT__ + for_each_available_child_of_node(dn, child) { + if (of_device_is_compatible(child, "brcm,nandcs")) { + struct brcmnand_host *host; + + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) { + of_node_put(child); + ret = -ENOMEM; + goto err; + } + host->pdev = pdev; + host->ctrl = ctrl; + + ret = brcmnand_init_cs(host, child); + if (ret) { + devm_kfree(dev, host); + continue; /* Try all chip-selects */ + } + + list_add_tail(&host->node, &ctrl->host_list); + } + } +#else + ofnode_for_each_subnode(child, dev_ofnode(dev)) { + if (ofnode_device_is_compatible(child, "brcm,nandcs")) { + struct brcmnand_host *host; + + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) { + ret = -ENOMEM; + goto err; + } + host->pdev = pdev; + host->ctrl = ctrl; + + ret = brcmnand_init_cs(host, child); + if (ret) { + devm_kfree(dev, host); + continue; /* Try all chip-selects */ + } + + list_add_tail(&host->node, &ctrl->host_list); + } + } +#endif /* __UBOOT__ */ + +err: +#ifndef __UBOOT__ + clk_disable_unprepare(ctrl->clk); +#else + if (ctrl->clk) + clk_disable(ctrl->clk); +#endif /* __UBOOT__ */ + return ret; + +} +EXPORT_SYMBOL_GPL(brcmnand_probe); + +#ifndef __UBOOT__ +int brcmnand_remove(struct platform_device *pdev) +{ + struct brcmnand_controller *ctrl = dev_get_drvdata(&pdev->dev); + struct brcmnand_host *host; + + list_for_each_entry(host, &ctrl->host_list, node) + nand_release(nand_to_mtd(&host->chip)); + + clk_disable_unprepare(ctrl->clk); + + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} +#else +int brcmnand_remove(struct udevice *pdev) +{ + return 0; +} +#endif /* __UBOOT__ */ +EXPORT_SYMBOL_GPL(brcmnand_remove); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Kevin Cernekee"); +MODULE_AUTHOR("Brian Norris"); +MODULE_DESCRIPTION("NAND driver for Broadcom chips"); +MODULE_ALIAS("platform:brcmnand"); diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.h b/drivers/mtd/nand/raw/brcmnand/brcmnand.h new file mode 100644 index 0000000000..6946a62b06 --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef __BRCMNAND_H__ +#define __BRCMNAND_H__ + +#include <linux/types.h> +#include <linux/io.h> + +struct brcmnand_soc { + bool (*ctlrdy_ack)(struct brcmnand_soc *soc); + void (*ctlrdy_set_enabled)(struct brcmnand_soc *soc, bool en); + void (*prepare_data_bus)(struct brcmnand_soc *soc, bool prepare, + bool is_param); + void *ctrl; +}; + +static inline void brcmnand_soc_data_bus_prepare(struct brcmnand_soc *soc, + bool is_param) +{ + if (soc && soc->prepare_data_bus) + soc->prepare_data_bus(soc, true, is_param); +} + +static inline void brcmnand_soc_data_bus_unprepare(struct brcmnand_soc *soc, + bool is_param) +{ + if (soc && soc->prepare_data_bus) + soc->prepare_data_bus(soc, false, is_param); +} + +static inline u32 brcmnand_readl(void __iomem *addr) +{ + /* + * MIPS endianness is configured by boot strap, which also reverses all + * bus endianness (i.e., big-endian CPU + big endian bus ==> native + * endian I/O). + * + * Other architectures (e.g., ARM) either do not support big endian, or + * else leave I/O in little endian mode. + */ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_SYS_BIG_ENDIAN)) + return __raw_readl(addr); + else + return readl_relaxed(addr); +} + +static inline void brcmnand_writel(u32 val, void __iomem *addr) +{ + /* See brcmnand_readl() comments */ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_SYS_BIG_ENDIAN)) + __raw_writel(val, addr); + else + writel_relaxed(val, addr); +} + +int brcmnand_probe(struct udevice *dev, struct brcmnand_soc *soc); +int brcmnand_remove(struct udevice *dev); + +#ifndef __UBOOT__ +extern const struct dev_pm_ops brcmnand_pm_ops; +#endif /* __UBOOT__ */ + +#endif /* __BRCMNAND_H__ */ diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand_compat.c b/drivers/mtd/nand/raw/brcmnand/brcmnand_compat.c new file mode 100644 index 0000000000..96b27e6e5a --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand_compat.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <common.h> +#include "brcmnand_compat.h" + +struct clk *devm_clk_get(struct udevice *dev, const char *id) +{ + struct clk *clk; + int ret; + + clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL); + if (!clk) { + debug("%s: can't allocate clock\n", __func__); + return ERR_PTR(-ENOMEM); + } + + ret = clk_get_by_name(dev, id, clk); + if (ret < 0) { + debug("%s: can't get clock (ret = %d)!\n", __func__, ret); + return ERR_PTR(ret); + } + + return clk; +} + +int clk_prepare_enable(struct clk *clk) +{ + return clk_enable(clk); +} + +void clk_disable_unprepare(struct clk *clk) +{ + clk_disable(clk); +} + +static char *devm_kvasprintf(struct udevice *dev, gfp_t gfp, const char *fmt, + va_list ap) +{ + unsigned int len; + char *p; + va_list aq; + + va_copy(aq, ap); + len = vsnprintf(NULL, 0, fmt, aq); + va_end(aq); + + p = devm_kmalloc(dev, len + 1, gfp); + if (!p) + return NULL; + + vsnprintf(p, len + 1, fmt, ap); + + return p; +} + +char *devm_kasprintf(struct udevice *dev, gfp_t gfp, const char *fmt, ...) +{ + va_list ap; + char *p; + + va_start(ap, fmt); + p = devm_kvasprintf(dev, gfp, fmt, ap); + va_end(ap); + + return p; +} diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand_compat.h b/drivers/mtd/nand/raw/brcmnand/brcmnand_compat.h new file mode 100644 index 0000000000..02cab0f828 --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand_compat.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef __BRCMNAND_COMPAT_H +#define __BRCMNAND_COMPAT_H + +#include <clk.h> +#include <dm.h> + +struct clk *devm_clk_get(struct udevice *dev, const char *id); +int clk_prepare_enable(struct clk *clk); +void clk_disable_unprepare(struct clk *clk); + +char *devm_kasprintf(struct udevice *dev, gfp_t gfp, const char *fmt, ...); + +#endif /* __BRCMNAND_COMPAT_H */ diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index 6d2ff58d86..e07bd6b657 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -486,14 +486,19 @@ static int nand_block_markbad_lowlevel(struct mtd_info *mtd, loff_t ofs) static int nand_check_wp(struct mtd_info *mtd) { struct nand_chip *chip = mtd_to_nand(mtd); + u8 status; + int ret; /* Broken xD cards report WP despite being writable */ if (chip->options & NAND_BROKEN_XD) return 0; /* Check the WP bit */ - chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); - return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1; + ret = nand_status_op(chip, &status); + if (ret) + return ret; + + return status & NAND_STATUS_WP ? 0 : 1; } /** @@ -575,11 +580,18 @@ static void nand_wait_status_ready(struct mtd_info *mtd, unsigned long timeo) { register struct nand_chip *chip = mtd_to_nand(mtd); u32 time_start; + int ret; timeo = (CONFIG_SYS_HZ * timeo) / 1000; time_start = get_timer(0); while (get_timer(time_start) < timeo) { - if ((chip->read_byte(mtd) & NAND_STATUS_READY)) + u8 status; + + ret = nand_read_data_op(chip, &status, sizeof(status), true); + if (ret) + return; + + if (status & NAND_STATUS_READY) break; WATCHDOG_RESET(); } @@ -851,7 +863,15 @@ static void panic_nand_wait(struct mtd_info *mtd, struct nand_chip *chip, if (chip->dev_ready(mtd)) break; } else { - if (chip->read_byte(mtd) & NAND_STATUS_READY) + int ret; + u8 status; + + ret = nand_read_data_op(chip, &status, sizeof(status), + true); + if (ret) + return; + + if (status & NAND_STATUS_READY) break; } mdelay(1); @@ -867,8 +887,9 @@ static void panic_nand_wait(struct mtd_info *mtd, struct nand_chip *chip, */ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) { - int status; unsigned long timeo = 400; + u8 status; + int ret; led_trigger_event(nand_led_trigger, LED_FULL); @@ -878,7 +899,9 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) */ ndelay(100); - chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + ret = nand_status_op(chip, NULL); + if (ret) + return ret; u32 timer = (CONFIG_SYS_HZ * timeo) / 1000; u32 time_start; @@ -889,13 +912,21 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) if (chip->dev_ready(mtd)) break; } else { - if (chip->read_byte(mtd) & NAND_STATUS_READY) + ret = nand_read_data_op(chip, &status, + sizeof(status), true); + if (ret) + return ret; + + if (status & NAND_STATUS_READY) break; } } led_trigger_event(nand_led_trigger, LED_OFF); - status = (int)chip->read_byte(mtd); + ret = nand_read_data_op(chip, &status, sizeof(status), true); + if (ret) + return ret; + /* This can happen if in case of timeout or buggy dev_ready */ WARN_ON(!(status & NAND_STATUS_READY)); return status; @@ -1048,6 +1079,516 @@ static void __maybe_unused nand_release_data_interface(struct nand_chip *chip) } /** + * nand_read_page_op - Do a READ PAGE operation + * @chip: The NAND chip + * @page: page to read + * @offset_in_page: offset within the page + * @buf: buffer used to store the data + * @len: length of the buffer + * + * This function issues a READ PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_read_page_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, void *buf, unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (len && !buf) + return -EINVAL; + + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + chip->cmdfunc(mtd, NAND_CMD_READ0, offset_in_page, page); + if (len) + chip->read_buf(mtd, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_read_page_op); + +/** + * nand_read_param_page_op - Do a READ PARAMETER PAGE operation + * @chip: The NAND chip + * @page: parameter page to read + * @buf: buffer used to store the data + * @len: length of the buffer + * + * This function issues a READ PARAMETER PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf, + unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int i; + u8 *p = buf; + + if (len && !buf) + return -EINVAL; + + chip->cmdfunc(mtd, NAND_CMD_PARAM, page, -1); + for (i = 0; i < len; i++) + p[i] = chip->read_byte(mtd); + + return 0; +} + +/** + * nand_change_read_column_op - Do a CHANGE READ COLUMN operation + * @chip: The NAND chip + * @offset_in_page: offset within the page + * @buf: buffer used to store the data + * @len: length of the buffer + * @force_8bit: force 8-bit bus access + * + * This function issues a CHANGE READ COLUMN operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_change_read_column_op(struct nand_chip *chip, + unsigned int offset_in_page, void *buf, + unsigned int len, bool force_8bit) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (len && !buf) + return -EINVAL; + + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset_in_page, -1); + if (len) + chip->read_buf(mtd, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_change_read_column_op); + +/** + * nand_read_oob_op - Do a READ OOB operation + * @chip: The NAND chip + * @page: page to read + * @offset_in_oob: offset within the OOB area + * @buf: buffer used to store the data + * @len: length of the buffer + * + * This function issues a READ OOB operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_read_oob_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_oob, void *buf, unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (len && !buf) + return -EINVAL; + + if (offset_in_oob + len > mtd->oobsize) + return -EINVAL; + + chip->cmdfunc(mtd, NAND_CMD_READOOB, offset_in_oob, page); + if (len) + chip->read_buf(mtd, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_read_oob_op); + +/** + * nand_prog_page_begin_op - starts a PROG PAGE operation + * @chip: The NAND chip + * @page: page to write + * @offset_in_page: offset within the page + * @buf: buffer containing the data to write to the page + * @len: length of the buffer + * + * This function issues the first half of a PROG PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, const void *buf, + unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (len && !buf) + return -EINVAL; + + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page); + + if (buf) + chip->write_buf(mtd, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_prog_page_begin_op); + +/** + * nand_prog_page_end_op - ends a PROG PAGE operation + * @chip: The NAND chip + * + * This function issues the second half of a PROG PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_prog_page_end_op(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int status; + + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = chip->waitfunc(mtd, chip); + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_prog_page_end_op); + +/** + * nand_prog_page_op - Do a full PROG PAGE operation + * @chip: The NAND chip + * @page: page to write + * @offset_in_page: offset within the page + * @buf: buffer containing the data to write to the page + * @len: length of the buffer + * + * This function issues a full PROG PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_prog_page_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, const void *buf, + unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int status; + + if (!len || !buf) + return -EINVAL; + + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, offset_in_page, page); + chip->write_buf(mtd, buf, len); + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = chip->waitfunc(mtd, chip); + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_prog_page_op); + +/** + * nand_change_write_column_op - Do a CHANGE WRITE COLUMN operation + * @chip: The NAND chip + * @offset_in_page: offset within the page + * @buf: buffer containing the data to send to the NAND + * @len: length of the buffer + * @force_8bit: force 8-bit bus access + * + * This function issues a CHANGE WRITE COLUMN operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_change_write_column_op(struct nand_chip *chip, + unsigned int offset_in_page, + const void *buf, unsigned int len, + bool force_8bit) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (len && !buf) + return -EINVAL; + + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset_in_page, -1); + if (len) + chip->write_buf(mtd, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_change_write_column_op); + +/** + * nand_readid_op - Do a READID operation + * @chip: The NAND chip + * @addr: address cycle to pass after the READID command + * @buf: buffer used to store the ID + * @len: length of the buffer + * + * This function sends a READID command and reads back the ID returned by the + * NAND. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf, + unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int i; + u8 *id = buf; + + if (len && !buf) + return -EINVAL; + + chip->cmdfunc(mtd, NAND_CMD_READID, addr, -1); + + for (i = 0; i < len; i++) + id[i] = chip->read_byte(mtd); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_readid_op); + +/** + * nand_status_op - Do a STATUS operation + * @chip: The NAND chip + * @status: out variable to store the NAND status + * + * This function sends a STATUS command and reads back the status returned by + * the NAND. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_status_op(struct nand_chip *chip, u8 *status) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + if (status) + *status = chip->read_byte(mtd); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_status_op); + +/** + * nand_exit_status_op - Exit a STATUS operation + * @chip: The NAND chip + * + * This function sends a READ0 command to cancel the effect of the STATUS + * command to avoid reading only the status until a new read command is sent. + * + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_exit_status_op(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + chip->cmdfunc(mtd, NAND_CMD_READ0, -1, -1); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_exit_status_op); + +/** + * nand_erase_op - Do an erase operation + * @chip: The NAND chip + * @eraseblock: block to erase + * + * This function sends an ERASE command and waits for the NAND to be ready + * before returning. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int page = eraseblock << + (chip->phys_erase_shift - chip->page_shift); + int status; + + chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); + chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); + + status = chip->waitfunc(mtd, chip); + if (status < 0) + return status; + + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_erase_op); + +/** + * nand_set_features_op - Do a SET FEATURES operation + * @chip: The NAND chip + * @feature: feature id + * @data: 4 bytes of data + * + * This function sends a SET FEATURES command and waits for the NAND to be + * ready before returning. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int nand_set_features_op(struct nand_chip *chip, u8 feature, + const void *data) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + const u8 *params = data; + int i, status; + + chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, feature, -1); + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) + chip->write_byte(mtd, params[i]); + + status = chip->waitfunc(mtd, chip); + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} + +/** + * nand_get_features_op - Do a GET FEATURES operation + * @chip: The NAND chip + * @feature: feature id + * @data: 4 bytes of data + * + * This function sends a GET FEATURES command and waits for the NAND to be + * ready before returning. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int nand_get_features_op(struct nand_chip *chip, u8 feature, + void *data) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + u8 *params = data; + int i; + + chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, feature, -1); + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) + params[i] = chip->read_byte(mtd); + + return 0; +} + +/** + * nand_reset_op - Do a reset operation + * @chip: The NAND chip + * + * This function sends a RESET command and waits for the NAND to be ready + * before returning. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_reset_op(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_reset_op); + +/** + * nand_read_data_op - Read data from the NAND + * @chip: The NAND chip + * @buf: buffer used to store the data + * @len: length of the buffer + * @force_8bit: force 8-bit bus access + * + * This function does a raw data read on the bus. Usually used after launching + * another NAND operation like nand_read_page_op(). + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len, + bool force_8bit) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (!len || !buf) + return -EINVAL; + + if (force_8bit) { + u8 *p = buf; + unsigned int i; + + for (i = 0; i < len; i++) + p[i] = chip->read_byte(mtd); + } else { + chip->read_buf(mtd, buf, len); + } + + return 0; +} +EXPORT_SYMBOL_GPL(nand_read_data_op); + +/** + * nand_write_data_op - Write data from the NAND + * @chip: The NAND chip + * @buf: buffer containing the data to send on the bus + * @len: length of the buffer + * @force_8bit: force 8-bit bus access + * + * This function does a raw data write on the bus. Usually used after launching + * another NAND operation like nand_write_page_begin_op(). + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_write_data_op(struct nand_chip *chip, const void *buf, + unsigned int len, bool force_8bit) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (!len || !buf) + return -EINVAL; + + if (force_8bit) { + const u8 *p = buf; + unsigned int i; + + for (i = 0; i < len; i++) + chip->write_byte(mtd, p[i]); + } else { + chip->write_buf(mtd, buf, len); + } + + return 0; +} +EXPORT_SYMBOL_GPL(nand_write_data_op); + +/** * nand_reset - Reset and initialize a NAND device * @chip: The NAND chip * @chipnr: Internal die id @@ -1068,8 +1609,10 @@ int nand_reset(struct nand_chip *chip, int chipnr) * interface settings, hence this weird ->select_chip() dance. */ chip->select_chip(mtd, chipnr); - chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + ret = nand_reset_op(chip); chip->select_chip(mtd, -1); + if (ret) + return ret; chip->select_chip(mtd, chipnr); ret = nand_setup_data_interface(chip, chipnr); @@ -1220,9 +1763,19 @@ EXPORT_SYMBOL(nand_check_erased_ecc_chunk); static int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int oob_required, int page) { - chip->read_buf(mtd, buf, mtd->writesize); - if (oob_required) - chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + int ret; + + ret = nand_read_data_op(chip, buf, mtd->writesize, false); + if (ret) + return ret; + + if (oob_required) { + ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, + false); + if (ret) + return ret; + } + return 0; } @@ -1243,29 +1796,46 @@ static int nand_read_page_raw_syndrome(struct mtd_info *mtd, int eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; uint8_t *oob = chip->oob_poi; - int steps, size; + int steps, size, ret; for (steps = chip->ecc.steps; steps > 0; steps--) { - chip->read_buf(mtd, buf, eccsize); + ret = nand_read_data_op(chip, buf, eccsize, false); + if (ret) + return ret; + buf += eccsize; if (chip->ecc.prepad) { - chip->read_buf(mtd, oob, chip->ecc.prepad); + ret = nand_read_data_op(chip, oob, chip->ecc.prepad, + false); + if (ret) + return ret; + oob += chip->ecc.prepad; } - chip->read_buf(mtd, oob, eccbytes); + ret = nand_read_data_op(chip, oob, eccbytes, false); + if (ret) + return ret; + oob += eccbytes; if (chip->ecc.postpad) { - chip->read_buf(mtd, oob, chip->ecc.postpad); + ret = nand_read_data_op(chip, oob, chip->ecc.postpad, + false); + if (ret) + return ret; + oob += chip->ecc.postpad; } } size = mtd->oobsize - (oob - chip->oob_poi); - if (size) - chip->read_buf(mtd, oob, size); + if (size) { + ret = nand_read_data_op(chip, oob, size, false); + if (ret) + return ret; + } return 0; } @@ -1336,6 +1906,7 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip, int busw = (chip->options & NAND_BUSWIDTH_16) ? 2 : 1; int index; unsigned int max_bitflips = 0; + int ret; /* Column address within the page aligned to ECC size (256bytes) */ start_step = data_offs / chip->ecc.size; @@ -1353,7 +1924,9 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip, chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_col_addr, -1); p = bufpoi + data_col_addr; - chip->read_buf(mtd, p, datafrag_len); + ret = nand_read_data_op(chip, p, datafrag_len, false); + if (ret) + return ret; /* Calculate ECC */ for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size) @@ -1370,8 +1943,11 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip, } } if (gaps) { - chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1); - chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + ret = nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, mtd->oobsize, + false); + if (ret) + return ret; } else { /* * Send the command to read the particular ECC bytes take care @@ -1384,9 +1960,12 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip, if (eccpos[index + (num_steps * chip->ecc.bytes)] & (busw - 1)) aligned_len++; - chip->cmdfunc(mtd, NAND_CMD_RNDOUT, - mtd->writesize + aligned_pos, -1); - chip->read_buf(mtd, &chip->oob_poi[aligned_pos], aligned_len); + ret = nand_change_read_column_op(chip, + mtd->writesize + aligned_pos, + &chip->oob_poi[aligned_pos], + aligned_len, false); + if (ret) + return ret; } for (i = 0; i < eccfrag_len; i++) @@ -1439,13 +2018,21 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *ecc_code = chip->buffers->ecccode; uint32_t *eccpos = chip->ecc.layout->eccpos; unsigned int max_bitflips = 0; + int ret; for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { chip->ecc.hwctl(mtd, NAND_ECC_READ); - chip->read_buf(mtd, p, eccsize); + + ret = nand_read_data_op(chip, p, eccsize, false); + if (ret) + return ret; + chip->ecc.calculate(mtd, p, &ecc_calc[i]); } - chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false); + if (ret) + return ret; for (i = 0; i < chip->ecc.total; i++) ecc_code[i] = chip->oob_poi[eccpos[i]]; @@ -1501,11 +2088,16 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd, uint32_t *eccpos = chip->ecc.layout->eccpos; uint8_t *ecc_calc = chip->buffers->ecccalc; unsigned int max_bitflips = 0; + int ret; /* Read the OOB area first */ - chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); - chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); - chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + ret = nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); + if (ret) + return ret; + + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + return ret; for (i = 0; i < chip->ecc.total; i++) ecc_code[i] = chip->oob_poi[eccpos[i]]; @@ -1514,7 +2106,11 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd, int stat; chip->ecc.hwctl(mtd, NAND_ECC_READ); - chip->read_buf(mtd, p, eccsize); + + ret = nand_read_data_op(chip, p, eccsize, false); + if (ret) + return ret; + chip->ecc.calculate(mtd, p, &ecc_calc[i]); stat = chip->ecc.correct(mtd, p, &ecc_code[i], NULL); @@ -1551,7 +2147,7 @@ static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd, static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int oob_required, int page) { - int i, eccsize = chip->ecc.size; + int ret, i, eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; int eccsteps = chip->ecc.steps; int eccpadbytes = eccbytes + chip->ecc.prepad + chip->ecc.postpad; @@ -1563,21 +2159,36 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip, int stat; chip->ecc.hwctl(mtd, NAND_ECC_READ); - chip->read_buf(mtd, p, eccsize); + + ret = nand_read_data_op(chip, p, eccsize, false); + if (ret) + return ret; if (chip->ecc.prepad) { - chip->read_buf(mtd, oob, chip->ecc.prepad); + ret = nand_read_data_op(chip, oob, chip->ecc.prepad, + false); + if (ret) + return ret; + oob += chip->ecc.prepad; } chip->ecc.hwctl(mtd, NAND_ECC_READSYN); - chip->read_buf(mtd, oob, eccbytes); + + ret = nand_read_data_op(chip, oob, eccbytes, false); + if (ret) + return ret; + stat = chip->ecc.correct(mtd, p, oob, NULL); oob += eccbytes; if (chip->ecc.postpad) { - chip->read_buf(mtd, oob, chip->ecc.postpad); + ret = nand_read_data_op(chip, oob, chip->ecc.postpad, + false); + if (ret) + return ret; + oob += chip->ecc.postpad; } @@ -1601,8 +2212,11 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip, /* Calculate remaining oob bytes */ i = mtd->oobsize - (oob - chip->oob_poi); - if (i) - chip->read_buf(mtd, oob, i); + if (i) { + ret = nand_read_data_op(chip, oob, i, false); + if (ret) + return ret; + } return max_bitflips; } @@ -1739,8 +2353,11 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, __func__, buf); read_retry: - if (nand_standard_page_accessors(&chip->ecc)) - chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); + if (nand_standard_page_accessors(&chip->ecc)) { + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + break; + } /* * Now read the page into the buffer. Absent an error, @@ -1874,9 +2491,7 @@ read_retry: static int nand_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page) { - chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); - chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); - return 0; + return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); } /** @@ -1893,25 +2508,43 @@ static int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip, int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad; int eccsize = chip->ecc.size; uint8_t *bufpoi = chip->oob_poi; - int i, toread, sndrnd = 0, pos; + int i, toread, sndrnd = 0, pos, ret; + + ret = nand_read_page_op(chip, page, chip->ecc.size, NULL, 0); + if (ret) + return ret; - chip->cmdfunc(mtd, NAND_CMD_READ0, chip->ecc.size, page); for (i = 0; i < chip->ecc.steps; i++) { if (sndrnd) { + int ret; + pos = eccsize + i * (eccsize + chunk); if (mtd->writesize > 512) - chip->cmdfunc(mtd, NAND_CMD_RNDOUT, pos, -1); + ret = nand_change_read_column_op(chip, pos, + NULL, 0, + false); else - chip->cmdfunc(mtd, NAND_CMD_READ0, pos, page); + ret = nand_read_page_op(chip, page, pos, NULL, + 0); + + if (ret) + return ret; } else sndrnd = 1; toread = min_t(int, length, chunk); - chip->read_buf(mtd, bufpoi, toread); + + ret = nand_read_data_op(chip, bufpoi, toread, false); + if (ret) + return ret; + bufpoi += toread; length -= toread; } - if (length > 0) - chip->read_buf(mtd, bufpoi, length); + if (length > 0) { + ret = nand_read_data_op(chip, bufpoi, length, false); + if (ret) + return ret; + } return 0; } @@ -1925,18 +2558,8 @@ static int nand_read_oob_syndrome(struct mtd_info *mtd, struct nand_chip *chip, static int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page) { - int status = 0; - const uint8_t *buf = chip->oob_poi; - int length = mtd->oobsize; - - chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page); - chip->write_buf(mtd, buf, length); - /* Send command to program the OOB data */ - chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); - - status = chip->waitfunc(mtd, chip); - - return status & NAND_STATUS_FAIL ? -EIO : 0; + return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi, + mtd->oobsize); } /** @@ -1951,7 +2574,7 @@ static int nand_write_oob_syndrome(struct mtd_info *mtd, { int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad; int eccsize = chip->ecc.size, length = mtd->oobsize; - int i, len, pos, status = 0, sndcmd = 0, steps = chip->ecc.steps; + int ret, i, len, pos, sndcmd = 0, steps = chip->ecc.steps; const uint8_t *bufpoi = chip->oob_poi; /* @@ -1965,7 +2588,10 @@ static int nand_write_oob_syndrome(struct mtd_info *mtd, } else pos = eccsize; - chip->cmdfunc(mtd, NAND_CMD_SEQIN, pos, page); + ret = nand_prog_page_begin_op(chip, page, pos, NULL, 0); + if (ret) + return ret; + for (i = 0; i < steps; i++) { if (sndcmd) { if (mtd->writesize <= 512) { @@ -1974,28 +2600,40 @@ static int nand_write_oob_syndrome(struct mtd_info *mtd, len = eccsize; while (len > 0) { int num = min_t(int, len, 4); - chip->write_buf(mtd, (uint8_t *)&fill, - num); + + ret = nand_write_data_op(chip, &fill, + num, false); + if (ret) + return ret; + len -= num; } } else { pos = eccsize + i * (eccsize + chunk); - chip->cmdfunc(mtd, NAND_CMD_RNDIN, pos, -1); + ret = nand_change_write_column_op(chip, pos, + NULL, 0, + false); + if (ret) + return ret; } } else sndcmd = 1; len = min_t(int, length, chunk); - chip->write_buf(mtd, bufpoi, len); + + ret = nand_write_data_op(chip, bufpoi, len, false); + if (ret) + return ret; + bufpoi += len; length -= len; } - if (length > 0) - chip->write_buf(mtd, bufpoi, length); - - chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); - status = chip->waitfunc(mtd, chip); + if (length > 0) { + ret = nand_write_data_op(chip, bufpoi, length, false); + if (ret) + return ret; + } - return status & NAND_STATUS_FAIL ? -EIO : 0; + return nand_prog_page_end_op(chip); } /** @@ -2154,9 +2792,18 @@ out: static int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t *buf, int oob_required, int page) { - chip->write_buf(mtd, buf, mtd->writesize); - if (oob_required) - chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + int ret; + + ret = nand_write_data_op(chip, buf, mtd->writesize, false); + if (ret) + return ret; + + if (oob_required) { + ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, + false); + if (ret) + return ret; + } return 0; } @@ -2179,29 +2826,46 @@ static int nand_write_page_raw_syndrome(struct mtd_info *mtd, int eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; uint8_t *oob = chip->oob_poi; - int steps, size; + int steps, size, ret; for (steps = chip->ecc.steps; steps > 0; steps--) { - chip->write_buf(mtd, buf, eccsize); + ret = nand_write_data_op(chip, buf, eccsize, false); + if (ret) + return ret; + buf += eccsize; if (chip->ecc.prepad) { - chip->write_buf(mtd, oob, chip->ecc.prepad); + ret = nand_write_data_op(chip, oob, chip->ecc.prepad, + false); + if (ret) + return ret; + oob += chip->ecc.prepad; } - chip->write_buf(mtd, oob, eccbytes); + ret = nand_write_data_op(chip, oob, eccbytes, false); + if (ret) + return ret; + oob += eccbytes; if (chip->ecc.postpad) { - chip->write_buf(mtd, oob, chip->ecc.postpad); + ret = nand_write_data_op(chip, oob, chip->ecc.postpad, + false); + if (ret) + return ret; + oob += chip->ecc.postpad; } } size = mtd->oobsize - (oob - chip->oob_poi); - if (size) - chip->write_buf(mtd, oob, size); + if (size) { + ret = nand_write_data_op(chip, oob, size, false); + if (ret) + return ret; + } return 0; } @@ -2252,17 +2916,24 @@ static int nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *ecc_calc = chip->buffers->ecccalc; const uint8_t *p = buf; uint32_t *eccpos = chip->ecc.layout->eccpos; + int ret; for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { chip->ecc.hwctl(mtd, NAND_ECC_WRITE); - chip->write_buf(mtd, p, eccsize); + + ret = nand_write_data_op(chip, p, eccsize, false); + if (ret) + return ret; + chip->ecc.calculate(mtd, p, &ecc_calc[i]); } for (i = 0; i < chip->ecc.total; i++) chip->oob_poi[eccpos[i]] = ecc_calc[i]; - chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false); + if (ret) + return ret; return 0; } @@ -2293,13 +2964,16 @@ static int nand_write_subpage_hwecc(struct mtd_info *mtd, uint32_t end_step = (offset + data_len - 1) / ecc_size; int oob_bytes = mtd->oobsize / ecc_steps; int step, i; + int ret; for (step = 0; step < ecc_steps; step++) { /* configure controller for WRITE access */ chip->ecc.hwctl(mtd, NAND_ECC_WRITE); /* write data (untouched subpages already masked by 0xFF) */ - chip->write_buf(mtd, buf, ecc_size); + ret = nand_write_data_op(chip, buf, ecc_size, false); + if (ret) + return ret; /* mask ECC of un-touched subpages by padding 0xFF */ if ((step < start_step) || (step > end_step)) @@ -2324,7 +2998,9 @@ static int nand_write_subpage_hwecc(struct mtd_info *mtd, chip->oob_poi[eccpos[i]] = ecc_calc[i]; /* write OOB buffer to NAND device */ - chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false); + if (ret) + return ret; return 0; } @@ -2351,31 +3027,49 @@ static int nand_write_page_syndrome(struct mtd_info *mtd, int eccsteps = chip->ecc.steps; const uint8_t *p = buf; uint8_t *oob = chip->oob_poi; + int ret; for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { - chip->ecc.hwctl(mtd, NAND_ECC_WRITE); - chip->write_buf(mtd, p, eccsize); + + ret = nand_write_data_op(chip, p, eccsize, false); + if (ret) + return ret; if (chip->ecc.prepad) { - chip->write_buf(mtd, oob, chip->ecc.prepad); + ret = nand_write_data_op(chip, oob, chip->ecc.prepad, + false); + if (ret) + return ret; + oob += chip->ecc.prepad; } chip->ecc.calculate(mtd, p, oob); - chip->write_buf(mtd, oob, eccbytes); + + ret = nand_write_data_op(chip, oob, eccbytes, false); + if (ret) + return ret; + oob += eccbytes; if (chip->ecc.postpad) { - chip->write_buf(mtd, oob, chip->ecc.postpad); + ret = nand_write_data_op(chip, oob, chip->ecc.postpad, + false); + if (ret) + return ret; + oob += chip->ecc.postpad; } } /* Calculate remaining oob bytes */ i = mtd->oobsize - (oob - chip->oob_poi); - if (i) - chip->write_buf(mtd, oob, i); + if (i) { + ret = nand_write_data_op(chip, oob, i, false); + if (ret) + return ret; + } return 0; } @@ -2403,8 +3097,11 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, else subpage = 0; - if (nand_standard_page_accessors(&chip->ecc)) - chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + if (nand_standard_page_accessors(&chip->ecc)) { + status = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (status) + return status; + } if (unlikely(raw)) status = chip->ecc.write_page_raw(mtd, chip, buf, @@ -2419,13 +3116,8 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, if (status < 0) return status; - if (nand_standard_page_accessors(&chip->ecc)) { - chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); - - status = chip->waitfunc(mtd, chip); - if (status & NAND_STATUS_FAIL) - return -EIO; - } + if (nand_standard_page_accessors(&chip->ecc)) + return nand_prog_page_end_op(chip); return 0; } @@ -2785,11 +3477,12 @@ out: static int single_erase(struct mtd_info *mtd, int page) { struct nand_chip *chip = mtd_to_nand(mtd); + unsigned int eraseblock; + /* Send commands to erase a block */ - chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page); - chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); + eraseblock = page >> (chip->phys_erase_shift - chip->page_shift); - return chip->waitfunc(mtd, chip); + return nand_erase_op(chip, eraseblock); } /** @@ -2982,9 +3675,6 @@ static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs) static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip, int addr, uint8_t *subfeature_param) { - int status; - int i; - #ifdef CONFIG_SYS_NAND_ONFI_DETECTION if (!chip->onfi_version || !(le16_to_cpu(chip->onfi_params.opt_cmd) @@ -2992,14 +3682,7 @@ static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip, return -ENOTSUPP; #endif - chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, addr, -1); - for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) - chip->write_byte(mtd, subfeature_param[i]); - - status = chip->waitfunc(mtd, chip); - if (status & NAND_STATUS_FAIL) - return -EIO; - return 0; + return nand_set_features_op(chip, addr, subfeature_param); } /** @@ -3012,8 +3695,6 @@ static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip, static int nand_onfi_get_features(struct mtd_info *mtd, struct nand_chip *chip, int addr, uint8_t *subfeature_param) { - int i; - #ifdef CONFIG_SYS_NAND_ONFI_DETECTION if (!chip->onfi_version || !(le16_to_cpu(chip->onfi_params.opt_cmd) @@ -3021,10 +3702,7 @@ static int nand_onfi_get_features(struct mtd_info *mtd, struct nand_chip *chip, return -ENOTSUPP; #endif - chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, addr, -1); - for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) - *subfeature_param++ = chip->read_byte(mtd); - return 0; + return nand_get_features_op(chip, addr, subfeature_param); } /* Set default functions */ @@ -3118,7 +3796,7 @@ static int nand_flash_detect_ext_param_page(struct mtd_info *mtd, struct onfi_ext_section *s; struct onfi_ext_ecc_info *ecc; uint8_t *cursor; - int ret = -EINVAL; + int ret; int len; int i; @@ -3128,14 +3806,18 @@ static int nand_flash_detect_ext_param_page(struct mtd_info *mtd, return -ENOMEM; /* Send our own NAND_CMD_PARAM. */ - chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1); + ret = nand_read_param_page_op(chip, 0, NULL, 0); + if (ret) + goto ext_out; /* Use the Change Read Column command to skip the ONFI param pages. */ - chip->cmdfunc(mtd, NAND_CMD_RNDOUT, - sizeof(*p) * p->num_of_param_pages , -1); + ret = nand_change_read_column_op(chip, + sizeof(*p) * p->num_of_param_pages, + ep, len, true); + if (ret) + goto ext_out; - /* Read out the Extended Parameter Page. */ - chip->read_buf(mtd, (uint8_t *)ep, len); + ret = -EINVAL; if ((onfi_crc16(ONFI_CRC_BASE, ((uint8_t *)ep) + 2, len - 2) != le16_to_cpu(ep->crc))) { pr_debug("fail in the CRC.\n"); @@ -3212,19 +3894,23 @@ static int nand_flash_detect_onfi(struct mtd_info *mtd, struct nand_chip *chip, int *busw) { struct nand_onfi_params *p = &chip->onfi_params; - int i, j; - int val; + char id[4]; + int i, ret, val; /* Try ONFI for unknown chip or LP */ - chip->cmdfunc(mtd, NAND_CMD_READID, 0x20, -1); - if (chip->read_byte(mtd) != 'O' || chip->read_byte(mtd) != 'N' || - chip->read_byte(mtd) != 'F' || chip->read_byte(mtd) != 'I') + ret = nand_readid_op(chip, 0x20, id, sizeof(id)); + if (ret || strncmp(id, "ONFI", 4)) + return 0; + + ret = nand_read_param_page_op(chip, 0, NULL, 0); + if (ret) return 0; - chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1); for (i = 0; i < 3; i++) { - for (j = 0; j < sizeof(*p); j++) - ((uint8_t *)p)[j] = chip->read_byte(mtd); + ret = nand_read_data_op(chip, p, sizeof(*p), true); + if (ret) + return 0; + if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 254) == le16_to_cpu(p->crc)) { break; @@ -3324,20 +4010,22 @@ static int nand_flash_detect_jedec(struct mtd_info *mtd, struct nand_chip *chip, { struct nand_jedec_params *p = &chip->jedec_params; struct jedec_ecc_info *ecc; - int val; - int i, j; + char id[5]; + int i, val, ret; /* Try JEDEC for unknown chip or LP */ - chip->cmdfunc(mtd, NAND_CMD_READID, 0x40, -1); - if (chip->read_byte(mtd) != 'J' || chip->read_byte(mtd) != 'E' || - chip->read_byte(mtd) != 'D' || chip->read_byte(mtd) != 'E' || - chip->read_byte(mtd) != 'C') + ret = nand_readid_op(chip, 0x40, id, sizeof(id)); + if (ret || strncmp(id, "JEDEC", sizeof(id))) + return 0; + + ret = nand_read_param_page_op(chip, 0x40, NULL, 0); + if (ret) return 0; - chip->cmdfunc(mtd, NAND_CMD_PARAM, 0x40, -1); for (i = 0; i < 3; i++) { - for (j = 0; j < sizeof(*p); j++) - ((uint8_t *)p)[j] = chip->read_byte(mtd); + ret = nand_read_data_op(chip, p, sizeof(*p), true); + if (ret) + return 0; if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 510) == le16_to_cpu(p->crc)) @@ -3708,25 +4396,29 @@ struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, int *maf_id, int *dev_id, struct nand_flash_dev *type) { - int busw; - int i, maf_idx; + int busw, ret; + int maf_idx; u8 id_data[8]; /* * Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx) * after power-up. */ - nand_reset(chip, 0); + ret = nand_reset(chip, 0); + if (ret) + return ERR_PTR(ret); /* Select the device */ chip->select_chip(mtd, 0); /* Send the command for reading device ID */ - chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); + ret = nand_readid_op(chip, 0, id_data, 2); + if (ret) + return ERR_PTR(ret); /* Read manufacturer and device IDs */ - *maf_id = chip->read_byte(mtd); - *dev_id = chip->read_byte(mtd); + *maf_id = id_data[0]; + *dev_id = id_data[1]; /* * Try again to make sure, as some systems the bus-hold or other @@ -3735,11 +4427,10 @@ struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, * not match, ignore the device completely. */ - chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); - /* Read entire ID string */ - for (i = 0; i < 8; i++) - id_data[i] = chip->read_byte(mtd); + ret = nand_readid_op(chip, 0, id_data, 8); + if (ret) + return ERR_PTR(ret); if (id_data[0] != *maf_id || id_data[1] != *dev_id) { pr_info("second ID read did not match %02x,%02x against %02x,%02x\n", @@ -3999,15 +4690,17 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips, /* Check for a chip array */ for (i = 1; i < maxchips; i++) { + u8 id[2]; + /* See comment in nand_get_flash_type for reset */ nand_reset(chip, i); chip->select_chip(mtd, i); /* Send the command for reading device ID */ - chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); + nand_readid_op(chip, 0, id, sizeof(id)); + /* Read manufacturer and device IDs */ - if (nand_maf_id != chip->read_byte(mtd) || - nand_dev_id != chip->read_byte(mtd)) { + if (nand_maf_id != id[0] || nand_dev_id != id[1]) { chip->select_chip(mtd, -1); break; } diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index d0e5426a99..6e436b56ab 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -528,4 +528,13 @@ config MEDIATEK_ETH This Driver support MediaTek Ethernet GMAC Say Y to enable support for the MediaTek Ethernet GMAC. +config HIGMACV300_ETH + bool "HiSilicon Gigabit Ethernet Controller" + depends on DM_ETH + select DM_RESET + select PHYLIB + help + This driver supports HIGMACV300 Ethernet controller found on + HiSilicon SoCs. + endif # NETDEVICES diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 51be72b0aa..8d02a37896 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -76,3 +76,4 @@ obj-$(CONFIG_SNI_AVE) += sni_ave.o obj-y += ti/ obj-$(CONFIG_MEDIATEK_ETH) += mtk_eth.o obj-y += mscc_eswitch/ +obj-$(CONFIG_HIGMACV300_ETH) += higmacv300.o diff --git a/drivers/net/higmacv300.c b/drivers/net/higmacv300.c new file mode 100644 index 0000000000..1be8359133 --- /dev/null +++ b/drivers/net/higmacv300.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019, Linaro Limited + */ + +#include <asm/io.h> +#include <common.h> +#include <console.h> +#include <linux/bug.h> +#include <linux/mii.h> +#include <miiphy.h> +#include <net.h> +#include <reset.h> +#include <wait_bit.h> + +#define STATION_ADDR_LOW 0x0000 +#define STATION_ADDR_HIGH 0x0004 +#define MAC_DUPLEX_HALF_CTRL 0x0008 +#define PORT_MODE 0x0040 +#define PORT_EN 0x0044 +#define BIT_TX_EN BIT(2) +#define BIT_RX_EN BIT(1) +#define MODE_CHANGE_EN 0x01b4 +#define BIT_MODE_CHANGE_EN BIT(0) +#define MDIO_SINGLE_CMD 0x03c0 +#define BIT_MDIO_BUSY BIT(20) +#define MDIO_READ (BIT(17) | BIT_MDIO_BUSY) +#define MDIO_WRITE (BIT(16) | BIT_MDIO_BUSY) +#define MDIO_SINGLE_DATA 0x03c4 +#define MDIO_RDATA_STATUS 0x03d0 +#define BIT_MDIO_RDATA_INVALID BIT(0) +#define RX_FQ_START_ADDR 0x0500 +#define RX_FQ_DEPTH 0x0504 +#define RX_FQ_WR_ADDR 0x0508 +#define RX_FQ_RD_ADDR 0x050c +#define RX_FQ_REG_EN 0x0518 +#define RX_BQ_START_ADDR 0x0520 +#define RX_BQ_DEPTH 0x0524 +#define RX_BQ_WR_ADDR 0x0528 +#define RX_BQ_RD_ADDR 0x052c +#define RX_BQ_REG_EN 0x0538 +#define TX_BQ_START_ADDR 0x0580 +#define TX_BQ_DEPTH 0x0584 +#define TX_BQ_WR_ADDR 0x0588 +#define TX_BQ_RD_ADDR 0x058c +#define TX_BQ_REG_EN 0x0598 +#define TX_RQ_START_ADDR 0x05a0 +#define TX_RQ_DEPTH 0x05a4 +#define TX_RQ_WR_ADDR 0x05a8 +#define TX_RQ_RD_ADDR 0x05ac +#define TX_RQ_REG_EN 0x05b8 +#define BIT_START_ADDR_EN BIT(2) +#define BIT_DEPTH_EN BIT(1) +#define DESC_WR_RD_ENA 0x05cc +#define BIT_RX_OUTCFF_WR BIT(3) +#define BIT_RX_CFF_RD BIT(2) +#define BIT_TX_OUTCFF_WR BIT(1) +#define BIT_TX_CFF_RD BIT(0) +#define BITS_DESC_ENA (BIT_RX_OUTCFF_WR | BIT_RX_CFF_RD | \ + BIT_TX_OUTCFF_WR | BIT_TX_CFF_RD) + +/* MACIF_CTRL */ +#define RGMII_SPEED_1000 0x2c +#define RGMII_SPEED_100 0x2f +#define RGMII_SPEED_10 0x2d +#define MII_SPEED_100 0x0f +#define MII_SPEED_10 0x0d +#define GMAC_SPEED_1000 0x05 +#define GMAC_SPEED_100 0x01 +#define GMAC_SPEED_10 0x00 +#define GMAC_FULL_DUPLEX BIT(4) + +#define RX_DESC_NUM 64 +#define TX_DESC_NUM 2 +#define DESC_SIZE 32 +#define DESC_WORD_SHIFT 3 +#define DESC_BYTE_SHIFT 5 +#define DESC_CNT(n) ((n) >> DESC_BYTE_SHIFT) +#define DESC_BYTE(n) ((n) << DESC_BYTE_SHIFT) +#define DESC_VLD_FREE 0 +#define DESC_VLD_BUSY 1 + +#define MAC_MAX_FRAME_SIZE 1600 + +enum higmac_queue { + RX_FQ, + RX_BQ, + TX_BQ, + TX_RQ, +}; + +struct higmac_desc { + unsigned int buf_addr; + unsigned int buf_len:11; + unsigned int reserve0:5; + unsigned int data_len:11; + unsigned int reserve1:2; + unsigned int fl:2; + unsigned int descvid:1; + unsigned int reserve2[6]; +}; + +struct higmac_priv { + void __iomem *base; + void __iomem *macif_ctrl; + struct reset_ctl rst_phy; + struct higmac_desc *rxfq; + struct higmac_desc *rxbq; + struct higmac_desc *txbq; + struct higmac_desc *txrq; + int rxdesc_in_use; + struct mii_dev *bus; + struct phy_device *phydev; + int phyintf; + int phyaddr; +}; + +#define flush_desc(d) flush_cache((unsigned long)(d), sizeof(*(d))) +#define invalidate_desc(d) \ + invalidate_dcache_range((unsigned long)(d), \ + (unsigned long)(d) + sizeof(*(d))) + +static int higmac_write_hwaddr(struct udevice *dev) +{ + struct eth_pdata *pdata = dev_get_platdata(dev); + struct higmac_priv *priv = dev_get_priv(dev); + unsigned char *mac = pdata->enetaddr; + u32 val; + + val = mac[1] | (mac[0] << 8); + writel(val, priv->base + STATION_ADDR_HIGH); + + val = mac[5] | (mac[4] << 8) | (mac[3] << 16) | (mac[2] << 24); + writel(val, priv->base + STATION_ADDR_LOW); + + return 0; +} + +static int higmac_free_pkt(struct udevice *dev, uchar *packet, int length) +{ + struct higmac_priv *priv = dev_get_priv(dev); + + /* Inform GMAC that the RX descriptor is no longer in use */ + writel(DESC_BYTE(priv->rxdesc_in_use), priv->base + RX_BQ_RD_ADDR); + + return 0; +} + +static int higmac_recv(struct udevice *dev, int flags, uchar **packetp) +{ + struct higmac_priv *priv = dev_get_priv(dev); + struct higmac_desc *fqd = priv->rxfq; + struct higmac_desc *bqd = priv->rxbq; + int fqw_pos, fqr_pos, bqw_pos, bqr_pos; + int timeout = 100000; + int len = 0; + int space; + int i; + + fqw_pos = DESC_CNT(readl(priv->base + RX_FQ_WR_ADDR)); + fqr_pos = DESC_CNT(readl(priv->base + RX_FQ_RD_ADDR)); + + if (fqw_pos >= fqr_pos) + space = RX_DESC_NUM - (fqw_pos - fqr_pos); + else + space = fqr_pos - fqw_pos; + + /* Leave one free to distinguish full filled from empty buffer */ + for (i = 0; i < space - 1; i++) { + fqd = priv->rxfq + fqw_pos; + invalidate_dcache_range(fqd->buf_addr, + fqd->buf_addr + MAC_MAX_FRAME_SIZE); + + if (++fqw_pos >= RX_DESC_NUM) + fqw_pos = 0; + + writel(DESC_BYTE(fqw_pos), priv->base + RX_FQ_WR_ADDR); + } + + bqr_pos = DESC_CNT(readl(priv->base + RX_BQ_RD_ADDR)); + bqd += bqr_pos; + /* BQ is only ever written by GMAC */ + invalidate_desc(bqd); + + do { + bqw_pos = DESC_CNT(readl(priv->base + RX_BQ_WR_ADDR)); + udelay(1); + } while (--timeout && bqw_pos == bqr_pos); + + if (!timeout) + return -ETIMEDOUT; + + if (++bqr_pos >= RX_DESC_NUM) + bqr_pos = 0; + + len = bqd->data_len; + + /* CPU should not have touched this buffer since we added it to FQ */ + invalidate_dcache_range(bqd->buf_addr, bqd->buf_addr + len); + *packetp = (void *)(unsigned long)bqd->buf_addr; + + /* Record the RX_BQ descriptor that is holding RX data */ + priv->rxdesc_in_use = bqr_pos; + + return len; +} + +static int higmac_send(struct udevice *dev, void *packet, int length) +{ + struct higmac_priv *priv = dev_get_priv(dev); + struct higmac_desc *bqd = priv->txbq; + int bqw_pos, rqw_pos, rqr_pos; + int timeout = 1000; + + flush_cache((unsigned long)packet, length); + + bqw_pos = DESC_CNT(readl(priv->base + TX_BQ_WR_ADDR)); + bqd += bqw_pos; + bqd->buf_addr = (unsigned long)packet; + bqd->descvid = DESC_VLD_BUSY; + bqd->data_len = length; + flush_desc(bqd); + + if (++bqw_pos >= TX_DESC_NUM) + bqw_pos = 0; + + writel(DESC_BYTE(bqw_pos), priv->base + TX_BQ_WR_ADDR); + + rqr_pos = DESC_CNT(readl(priv->base + TX_RQ_RD_ADDR)); + if (++rqr_pos >= TX_DESC_NUM) + rqr_pos = 0; + + do { + rqw_pos = DESC_CNT(readl(priv->base + TX_RQ_WR_ADDR)); + udelay(1); + } while (--timeout && rqr_pos != rqw_pos); + + if (!timeout) + return -ETIMEDOUT; + + writel(DESC_BYTE(rqr_pos), priv->base + TX_RQ_RD_ADDR); + + return 0; +} + +static int higmac_adjust_link(struct higmac_priv *priv) +{ + struct phy_device *phydev = priv->phydev; + int interface = priv->phyintf; + u32 val; + + switch (interface) { + case PHY_INTERFACE_MODE_RGMII: + if (phydev->speed == SPEED_1000) + val = RGMII_SPEED_1000; + else if (phydev->speed == SPEED_100) + val = RGMII_SPEED_100; + else + val = RGMII_SPEED_10; + break; + case PHY_INTERFACE_MODE_MII: + if (phydev->speed == SPEED_100) + val = MII_SPEED_100; + else + val = MII_SPEED_10; + break; + default: + debug("unsupported mode: %d\n", interface); + return -EINVAL; + } + + if (phydev->duplex) + val |= GMAC_FULL_DUPLEX; + + writel(val, priv->macif_ctrl); + + if (phydev->speed == SPEED_1000) + val = GMAC_SPEED_1000; + else if (phydev->speed == SPEED_100) + val = GMAC_SPEED_100; + else + val = GMAC_SPEED_10; + + writel(BIT_MODE_CHANGE_EN, priv->base + MODE_CHANGE_EN); + writel(val, priv->base + PORT_MODE); + writel(0, priv->base + MODE_CHANGE_EN); + writel(phydev->duplex, priv->base + MAC_DUPLEX_HALF_CTRL); + + return 0; +} + +static int higmac_start(struct udevice *dev) +{ + struct higmac_priv *priv = dev_get_priv(dev); + struct phy_device *phydev = priv->phydev; + int ret; + + ret = phy_startup(phydev); + if (ret) + return ret; + + if (!phydev->link) { + debug("%s: link down\n", phydev->dev->name); + return -ENODEV; + } + + ret = higmac_adjust_link(priv); + if (ret) + return ret; + + /* Enable port */ + writel(BITS_DESC_ENA, priv->base + DESC_WR_RD_ENA); + writel(BIT_TX_EN | BIT_RX_EN, priv->base + PORT_EN); + + return 0; +} + +static void higmac_stop(struct udevice *dev) +{ + struct higmac_priv *priv = dev_get_priv(dev); + + /* Disable port */ + writel(0, priv->base + PORT_EN); + writel(0, priv->base + DESC_WR_RD_ENA); +} + +static const struct eth_ops higmac_ops = { + .start = higmac_start, + .send = higmac_send, + .recv = higmac_recv, + .free_pkt = higmac_free_pkt, + .stop = higmac_stop, + .write_hwaddr = higmac_write_hwaddr, +}; + +static int higmac_mdio_read(struct mii_dev *bus, int addr, int devad, int reg) +{ + struct higmac_priv *priv = bus->priv; + int ret; + + ret = wait_for_bit_le32(priv->base + MDIO_SINGLE_CMD, BIT_MDIO_BUSY, + false, 1000, false); + if (ret) + return ret; + + writel(MDIO_READ | addr << 8 | reg, priv->base + MDIO_SINGLE_CMD); + + ret = wait_for_bit_le32(priv->base + MDIO_SINGLE_CMD, BIT_MDIO_BUSY, + false, 1000, false); + if (ret) + return ret; + + if (readl(priv->base + MDIO_RDATA_STATUS) & BIT_MDIO_RDATA_INVALID) + return -EINVAL; + + return readl(priv->base + MDIO_SINGLE_DATA) >> 16; +} + +static int higmac_mdio_write(struct mii_dev *bus, int addr, int devad, + int reg, u16 value) +{ + struct higmac_priv *priv = bus->priv; + int ret; + + ret = wait_for_bit_le32(priv->base + MDIO_SINGLE_CMD, BIT_MDIO_BUSY, + false, 1000, false); + if (ret) + return ret; + + writel(value, priv->base + MDIO_SINGLE_DATA); + writel(MDIO_WRITE | addr << 8 | reg, priv->base + MDIO_SINGLE_CMD); + + return 0; +} + +static int higmac_init_rx_descs(struct higmac_desc *descs, int num) +{ + int i; + + for (i = 0; i < num; i++) { + struct higmac_desc *desc = &descs[i]; + + desc->buf_addr = (unsigned long)memalign(ARCH_DMA_MINALIGN, + MAC_MAX_FRAME_SIZE); + if (!desc->buf_addr) + goto free_bufs; + + desc->descvid = DESC_VLD_FREE; + desc->buf_len = MAC_MAX_FRAME_SIZE - 1; + flush_desc(desc); + } + + return 0; + +free_bufs: + while (--i > 0) + free((void *)(unsigned long)descs[i].buf_addr); + return -ENOMEM; +} + +static int higmac_init_hw_queue(struct higmac_priv *priv, + enum higmac_queue queue) +{ + struct higmac_desc *desc, **pdesc; + u32 regaddr, regen, regdep; + int depth; + int len; + + switch (queue) { + case RX_FQ: + regaddr = RX_FQ_START_ADDR; + regen = RX_FQ_REG_EN; + regdep = RX_FQ_DEPTH; + depth = RX_DESC_NUM; + pdesc = &priv->rxfq; + break; + case RX_BQ: + regaddr = RX_BQ_START_ADDR; + regen = RX_BQ_REG_EN; + regdep = RX_BQ_DEPTH; + depth = RX_DESC_NUM; + pdesc = &priv->rxbq; + break; + case TX_BQ: + regaddr = TX_BQ_START_ADDR; + regen = TX_BQ_REG_EN; + regdep = TX_BQ_DEPTH; + depth = TX_DESC_NUM; + pdesc = &priv->txbq; + break; + case TX_RQ: + regaddr = TX_RQ_START_ADDR; + regen = TX_RQ_REG_EN; + regdep = TX_RQ_DEPTH; + depth = TX_DESC_NUM; + pdesc = &priv->txrq; + break; + } + + /* Enable depth */ + writel(BIT_DEPTH_EN, priv->base + regen); + writel(depth << DESC_WORD_SHIFT, priv->base + regdep); + writel(0, priv->base + regen); + + len = depth * sizeof(*desc); + desc = memalign(ARCH_DMA_MINALIGN, len); + if (!desc) + return -ENOMEM; + memset(desc, 0, len); + flush_cache((unsigned long)desc, len); + *pdesc = desc; + + /* Set up RX_FQ descriptors */ + if (queue == RX_FQ) + higmac_init_rx_descs(desc, depth); + + /* Enable start address */ + writel(BIT_START_ADDR_EN, priv->base + regen); + writel((unsigned long)desc, priv->base + regaddr); + writel(0, priv->base + regen); + + return 0; +} + +static int higmac_hw_init(struct higmac_priv *priv) +{ + int ret; + + /* Initialize hardware queues */ + ret = higmac_init_hw_queue(priv, RX_FQ); + if (ret) + return ret; + + ret = higmac_init_hw_queue(priv, RX_BQ); + if (ret) + goto free_rx_fq; + + ret = higmac_init_hw_queue(priv, TX_BQ); + if (ret) + goto free_rx_bq; + + ret = higmac_init_hw_queue(priv, TX_RQ); + if (ret) + goto free_tx_bq; + + /* Reset phy */ + reset_deassert(&priv->rst_phy); + mdelay(10); + reset_assert(&priv->rst_phy); + mdelay(30); + reset_deassert(&priv->rst_phy); + mdelay(30); + + return 0; + +free_tx_bq: + free(priv->txbq); +free_rx_bq: + free(priv->rxbq); +free_rx_fq: + free(priv->rxfq); + return ret; +} + +static int higmac_probe(struct udevice *dev) +{ + struct higmac_priv *priv = dev_get_priv(dev); + struct phy_device *phydev; + struct mii_dev *bus; + int ret; + + ret = higmac_hw_init(priv); + if (ret) + return ret; + + bus = mdio_alloc(); + if (!bus) + return -ENOMEM; + + bus->read = higmac_mdio_read; + bus->write = higmac_mdio_write; + bus->priv = priv; + priv->bus = bus; + + ret = mdio_register_seq(bus, dev->seq); + if (ret) + return ret; + + phydev = phy_connect(bus, priv->phyaddr, dev, priv->phyintf); + if (!phydev) + return -ENODEV; + + phydev->supported &= PHY_GBIT_FEATURES; + phydev->advertising = phydev->supported; + priv->phydev = phydev; + + return phy_config(phydev); +} + +static int higmac_remove(struct udevice *dev) +{ + struct higmac_priv *priv = dev_get_priv(dev); + int i; + + mdio_unregister(priv->bus); + mdio_free(priv->bus); + + /* Free RX packet buffers */ + for (i = 0; i < RX_DESC_NUM; i++) + free((void *)(unsigned long)priv->rxfq[i].buf_addr); + + return 0; +} + +static int higmac_ofdata_to_platdata(struct udevice *dev) +{ + struct higmac_priv *priv = dev_get_priv(dev); + int phyintf = PHY_INTERFACE_MODE_NONE; + const char *phy_mode; + ofnode phy_node; + + priv->base = dev_remap_addr_index(dev, 0); + priv->macif_ctrl = dev_remap_addr_index(dev, 1); + + phy_mode = dev_read_string(dev, "phy-mode"); + if (phy_mode) + phyintf = phy_get_interface_by_name(phy_mode); + if (phyintf == PHY_INTERFACE_MODE_NONE) + return -ENODEV; + priv->phyintf = phyintf; + + phy_node = dev_read_subnode(dev, "phy"); + if (!ofnode_valid(phy_node)) { + debug("failed to find phy node\n"); + return -ENODEV; + } + priv->phyaddr = ofnode_read_u32_default(phy_node, "reg", 0); + + return reset_get_by_name(dev, "phy", &priv->rst_phy); +} + +static const struct udevice_id higmac_ids[] = { + { .compatible = "hisilicon,hi3798cv200-gmac" }, + { } +}; + +U_BOOT_DRIVER(eth_higmac) = { + .name = "eth_higmac", + .id = UCLASS_ETH, + .of_match = higmac_ids, + .ofdata_to_platdata = higmac_ofdata_to_platdata, + .probe = higmac_probe, + .remove = higmac_remove, + .ops = &higmac_ops, + .priv_auto_alloc_size = sizeof(struct higmac_priv), + .platdata_auto_alloc_size = sizeof(struct eth_pdata), +}; diff --git a/drivers/pci/pci-uclass.c b/drivers/pci/pci-uclass.c index 824fa11907..cf1e7617ae 100644 --- a/drivers/pci/pci-uclass.c +++ b/drivers/pci/pci-uclass.c @@ -918,6 +918,11 @@ static void decode_regions(struct pci_controller *hose, ofnode parent_node, return; for (i = 0; i < CONFIG_NR_DRAM_BANKS; ++i) { + if (hose->region_count == MAX_PCI_REGIONS) { + pr_err("maximum number of regions parsed, aborting\n"); + break; + } + if (bd->bi_dram[i].size) { pci_set_region(hose->regions + hose->region_count++, bd->bi_dram[i].start, diff --git a/drivers/pinctrl/mediatek/Kconfig b/drivers/pinctrl/mediatek/Kconfig index 1bd9a925a5..9930ca1faf 100644 --- a/drivers/pinctrl/mediatek/Kconfig +++ b/drivers/pinctrl/mediatek/Kconfig @@ -12,4 +12,8 @@ config PINCTRL_MT7629 bool "MT7629 SoC pinctrl driver" select PINCTRL_MTK +config PINCTRL_MT8516 + bool "MT8516 SoC pinctrl driver" + select PINCTRL_MTK + endif diff --git a/drivers/pinctrl/mediatek/Makefile b/drivers/pinctrl/mediatek/Makefile index f6ef3627e8..c4f29088d2 100644 --- a/drivers/pinctrl/mediatek/Makefile +++ b/drivers/pinctrl/mediatek/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_PINCTRL_MTK) += pinctrl-mtk-common.o # SoC Drivers obj-$(CONFIG_PINCTRL_MT7623) += pinctrl-mt7623.o obj-$(CONFIG_PINCTRL_MT7629) += pinctrl-mt7629.o +obj-$(CONFIG_PINCTRL_MT8516) += pinctrl-mt8516.o diff --git a/drivers/pinctrl/mediatek/pinctrl-mt8516.c b/drivers/pinctrl/mediatek/pinctrl-mt8516.c new file mode 100644 index 0000000000..829b30e5a2 --- /dev/null +++ b/drivers/pinctrl/mediatek/pinctrl-mt8516.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Fabien Parent <fparent@baylibre.com> + */ + +#include <dm.h> + +#include "pinctrl-mtk-common.h" + +#define PIN_FIELD(_s_pin, _e_pin, _s_addr, _x_addrs, _s_bit, _x_bits) \ + PIN_FIELD_CALC(_s_pin, _e_pin, _s_addr, _x_addrs, _s_bit, \ + _x_bits, 16, false) + +static const struct mtk_pin_field_calc mt8516_pin_mode_range[] = { + PIN_FIELD_CALC(0, 124, 0x300, 0x10, 0, 3, 15, false), +}; + +static const struct mtk_pin_field_calc mt8516_pin_dir_range[] = { + PIN_FIELD(0, 124, 0x0, 0x10, 0, 1), +}; + +static const struct mtk_pin_field_calc mt8516_pin_di_range[] = { + PIN_FIELD(0, 124, 0x200, 0x10, 0, 1), +}; + +static const struct mtk_pin_field_calc mt8516_pin_do_range[] = { + PIN_FIELD(0, 124, 0x100, 0x10, 0, 1), +}; + +static const struct mtk_pin_field_calc mt8516_pin_ies_range[] = { + PIN_FIELD(0, 6, 0x900, 0x10, 2, 1), + PIN_FIELD(7, 10, 0x900, 0x10, 3, 1), + PIN_FIELD(11, 13, 0x900, 0x10, 12, 1), + PIN_FIELD(14, 17, 0x900, 0x10, 13, 1), + PIN_FIELD(18, 20, 0x910, 0x10, 10, 1), + PIN_FIELD(21, 23, 0x900, 0x10, 13, 1), + PIN_FIELD(24, 25, 0x900, 0x10, 12, 1), + PIN_FIELD(26, 30, 0x900, 0x10, 0, 1), + PIN_FIELD(31, 33, 0x900, 0x10, 1, 1), + PIN_FIELD(34, 39, 0x900, 0x10, 2, 1), + PIN_FIELD(40, 40, 0x910, 0x10, 11, 1), + PIN_FIELD(41, 43, 0x900, 0x10, 10, 1), + PIN_FIELD(44, 47, 0x900, 0x10, 11, 1), + PIN_FIELD(48, 51, 0x900, 0x10, 14, 1), + PIN_FIELD(52, 53, 0x910, 0x10, 0, 1), + PIN_FIELD(54, 54, 0x910, 0x10, 2, 1), + PIN_FIELD(55, 57, 0x910, 0x10, 4, 1), + PIN_FIELD(58, 59, 0x900, 0x10, 15, 1), + PIN_FIELD(60, 61, 0x910, 0x10, 1, 1), + PIN_FIELD(62, 65, 0x910, 0x10, 5, 1), + PIN_FIELD(66, 67, 0x910, 0x10, 6, 1), + PIN_FIELD(68, 68, 0x930, 0x10, 2, 1), + PIN_FIELD(69, 69, 0x930, 0x10, 1, 1), + PIN_FIELD(70, 70, 0x930, 0x10, 6, 1), + PIN_FIELD(71, 71, 0x930, 0x10, 5, 1), + PIN_FIELD(72, 72, 0x930, 0x10, 4, 1), + PIN_FIELD(73, 73, 0x930, 0x10, 3, 1), + + PIN_FIELD(100, 103, 0x910, 0x10, 7, 1), + PIN_FIELD(104, 104, 0x920, 0x10, 12, 1), + PIN_FIELD(105, 105, 0x920, 0x10, 11, 1), + PIN_FIELD(106, 106, 0x930, 0x10, 0, 1), + PIN_FIELD(107, 107, 0x920, 0x10, 15, 1), + PIN_FIELD(108, 108, 0x920, 0x10, 14, 1), + PIN_FIELD(109, 109, 0x920, 0x10, 13, 1), + PIN_FIELD(110, 110, 0x920, 0x10, 9, 1), + PIN_FIELD(111, 111, 0x920, 0x10, 8, 1), + PIN_FIELD(112, 112, 0x920, 0x10, 7, 1), + PIN_FIELD(113, 113, 0x920, 0x10, 6, 1), + PIN_FIELD(114, 114, 0x920, 0x10, 10, 1), + PIN_FIELD(115, 115, 0x920, 0x10, 1, 1), + PIN_FIELD(116, 116, 0x920, 0x10, 0, 1), + PIN_FIELD(117, 117, 0x920, 0x10, 5, 1), + PIN_FIELD(118, 118, 0x920, 0x10, 4, 1), + PIN_FIELD(119, 119, 0x920, 0x10, 3, 1), + PIN_FIELD(120, 120, 0x920, 0x10, 2, 1), + PIN_FIELD(121, 124, 0x910, 0x10, 9, 1), +}; + +static const struct mtk_pin_field_calc mt8516_pin_smt_range[] = { + PIN_FIELD(0, 6, 0xA00, 0x10, 2, 1), + PIN_FIELD(7, 10, 0xA00, 0x10, 3, 1), + PIN_FIELD(11, 13, 0xA00, 0x10, 12, 1), + PIN_FIELD(14, 17, 0xA00, 0x10, 13, 1), + PIN_FIELD(18, 20, 0xA10, 0x10, 10, 1), + PIN_FIELD(21, 23, 0xA00, 0x10, 13, 1), + PIN_FIELD(24, 25, 0xA00, 0x10, 12, 1), + PIN_FIELD(26, 30, 0xA00, 0x10, 0, 1), + PIN_FIELD(31, 33, 0xA00, 0x10, 1, 1), + PIN_FIELD(40, 40, 0xA10, 0x10, 11, 1), + PIN_FIELD(41, 43, 0xA00, 0x10, 10, 1), + PIN_FIELD(44, 47, 0xA00, 0x10, 11, 1), + PIN_FIELD(48, 51, 0xA00, 0x10, 14, 1), + PIN_FIELD(52, 53, 0xA10, 0x10, 0, 1), + PIN_FIELD(54, 54, 0xA10, 0x10, 2, 1), + PIN_FIELD(55, 57, 0xA10, 0x10, 4, 1), + PIN_FIELD(58, 59, 0xA00, 0x10, 15, 1), + PIN_FIELD(60, 61, 0xA10, 0x10, 1, 1), + PIN_FIELD(62, 65, 0xA10, 0x10, 5, 1), + PIN_FIELD(66, 67, 0xA10, 0x10, 6, 1), + PIN_FIELD(68, 68, 0xA30, 0x10, 2, 1), + PIN_FIELD(69, 69, 0xA30, 0x10, 1, 1), + PIN_FIELD(70, 70, 0xA30, 0x10, 3, 1), + PIN_FIELD(71, 71, 0xA30, 0x10, 4, 1), + PIN_FIELD(72, 72, 0xA30, 0x10, 5, 1), + PIN_FIELD(73, 73, 0xA30, 0x10, 6, 1), + + PIN_FIELD(100, 103, 0xA10, 0x10, 7, 1), + PIN_FIELD(104, 104, 0xA20, 0x10, 12, 1), + PIN_FIELD(105, 105, 0xA20, 0x10, 11, 1), + PIN_FIELD(106, 106, 0xA30, 0x10, 13, 1), + PIN_FIELD(107, 107, 0xA20, 0x10, 14, 1), + PIN_FIELD(108, 108, 0xA20, 0x10, 15, 1), + PIN_FIELD(109, 109, 0xA30, 0x10, 0, 1), + PIN_FIELD(110, 110, 0xA20, 0x10, 9, 1), + PIN_FIELD(111, 111, 0xA20, 0x10, 8, 1), + PIN_FIELD(112, 112, 0xA20, 0x10, 7, 1), + PIN_FIELD(113, 113, 0xA20, 0x10, 6, 1), + PIN_FIELD(114, 114, 0xA20, 0x10, 10, 1), + PIN_FIELD(115, 115, 0xA20, 0x10, 1, 1), + PIN_FIELD(116, 116, 0xA20, 0x10, 0, 1), + PIN_FIELD(117, 117, 0xA20, 0x10, 5, 1), + PIN_FIELD(118, 118, 0xA20, 0x10, 4, 1), + PIN_FIELD(119, 119, 0xA20, 0x10, 3, 1), + PIN_FIELD(120, 120, 0xA20, 0x10, 2, 1), + PIN_FIELD(121, 124, 0xA10, 0x10, 9, 1), +}; + +static const struct mtk_pin_field_calc mt8516_pin_pullen_range[] = { + PIN_FIELD(0, 13, 0x500, 0x10, 0, 1), + PIN_FIELD(18, 20, 0x510, 0x10, 2, 1), + PIN_FIELD(24, 31, 0x510, 0x10, 8, 1), + PIN_FIELD(32, 39, 0x520, 0x10, 0, 1), + PIN_FIELD(44, 47, 0x520, 0x10, 12, 1), + PIN_FIELD(48, 63, 0x530, 0x10, 0, 1), + PIN_FIELD(64, 67, 0x540, 0x10, 0, 1), + PIN_FIELD(100, 103, 0x560, 0x10, 4, 1), + PIN_FIELD(121, 124, 0x570, 0x10, 9, 1), +}; + +static const struct mtk_pin_field_calc mt8516_pin_pullsel_range[] = { + PIN_FIELD(0, 13, 0x600, 0x10, 0, 1), + PIN_FIELD(18, 20, 0x610, 0x10, 2, 1), + PIN_FIELD(24, 31, 0x610, 0x10, 8, 1), + PIN_FIELD(32, 39, 0x620, 0x10, 0, 1), + PIN_FIELD(44, 47, 0x620, 0x10, 12, 1), + PIN_FIELD(48, 63, 0x630, 0x10, 0, 1), + PIN_FIELD(64, 67, 0x640, 0x10, 0, 1), + PIN_FIELD(100, 103, 0x660, 0x10, 4, 1), + PIN_FIELD(121, 124, 0x670, 0x10, 9, 1), +}; + +static const struct mtk_pin_field_calc mt8516_pin_drv_range[] = { + PIN_FIELD(0, 4, 0xd00, 0x10, 0, 4), + PIN_FIELD(5, 10, 0xd00, 0x10, 4, 4), + PIN_FIELD(11, 13, 0xd00, 0x10, 8, 4), + PIN_FIELD(14, 17, 0xd00, 0x10, 12, 4), + PIN_FIELD(18, 20, 0xd10, 0x10, 0, 4), + PIN_FIELD(21, 23, 0xd00, 0x10, 12, 4), + PIN_FIELD(24, 25, 0xd00, 0x10, 8, 4), + PIN_FIELD(26, 30, 0xd10, 0x10, 4, 4), + PIN_FIELD(31, 33, 0xd10, 0x10, 8, 4), + PIN_FIELD(34, 35, 0xd10, 0x10, 12, 4), + PIN_FIELD(36, 39, 0xd20, 0x10, 0, 4), + PIN_FIELD(40, 40, 0xd20, 0x10, 4, 4), + PIN_FIELD(41, 43, 0xd20, 0x10, 8, 4), + PIN_FIELD(44, 47, 0xd20, 0x10, 12, 4), + PIN_FIELD(48, 51, 0xd30, 0x10, 0, 4), + PIN_FIELD(54, 54, 0xd30, 0x10, 8, 4), + PIN_FIELD(55, 57, 0xd30, 0x10, 12, 4), + PIN_FIELD(62, 67, 0xd40, 0x10, 8, 4), + PIN_FIELD(68, 68, 0xd40, 0x10, 12, 4), + PIN_FIELD(69, 69, 0xd50, 0x10, 0, 4), + PIN_FIELD(70, 73, 0xd50, 0x10, 4, 4), + PIN_FIELD(100, 103, 0xd50, 0x10, 8, 4), + PIN_FIELD(104, 104, 0xd50, 0x10, 12, 4), + PIN_FIELD(105, 105, 0xd60, 0x10, 0, 4), + PIN_FIELD(106, 109, 0xd60, 0x10, 4, 4), + PIN_FIELD(110, 113, 0xd70, 0x10, 0, 4), + PIN_FIELD(114, 114, 0xd70, 0x10, 4, 4), + PIN_FIELD(115, 115, 0xd60, 0x10, 12, 4), + PIN_FIELD(116, 116, 0xd60, 0x10, 8, 4), + PIN_FIELD(117, 120, 0xd70, 0x10, 0, 4), +}; + +static const struct mtk_pin_reg_calc mt8516_reg_cals[] = { + [PINCTRL_PIN_REG_MODE] = MTK_RANGE(mt8516_pin_mode_range), + [PINCTRL_PIN_REG_DIR] = MTK_RANGE(mt8516_pin_dir_range), + [PINCTRL_PIN_REG_DI] = MTK_RANGE(mt8516_pin_di_range), + [PINCTRL_PIN_REG_DO] = MTK_RANGE(mt8516_pin_do_range), + [PINCTRL_PIN_REG_IES] = MTK_RANGE(mt8516_pin_ies_range), + [PINCTRL_PIN_REG_SMT] = MTK_RANGE(mt8516_pin_smt_range), + [PINCTRL_PIN_REG_PULLSEL] = MTK_RANGE(mt8516_pin_pullsel_range), + [PINCTRL_PIN_REG_PULLEN] = MTK_RANGE(mt8516_pin_pullen_range), + [PINCTRL_PIN_REG_DRV] = MTK_RANGE(mt8516_pin_drv_range), +}; + +static const struct mtk_pin_desc mt8516_pins[] = { + MTK_PIN(0, "EINT0", DRV_GRP0), + MTK_PIN(1, "EINT1", DRV_GRP0), + MTK_PIN(2, "EINT2", DRV_GRP0), + MTK_PIN(3, "EINT3", DRV_GRP0), + MTK_PIN(4, "EINT4", DRV_GRP0), + MTK_PIN(5, "EINT5", DRV_GRP0), + MTK_PIN(6, "EINT6", DRV_GRP0), + MTK_PIN(7, "EINT7", DRV_GRP0), + MTK_PIN(8, "EINT8", DRV_GRP0), + MTK_PIN(9, "EINT9", DRV_GRP0), + MTK_PIN(10, "EINT10", DRV_GRP0), + MTK_PIN(11, "EINT11", DRV_GRP0), + MTK_PIN(12, "EINT12", DRV_GRP0), + MTK_PIN(13, "EINT13", DRV_GRP0), + MTK_PIN(14, "EINT14", DRV_GRP2), + MTK_PIN(15, "EINT15", DRV_GRP2), + MTK_PIN(16, "EINT16", DRV_GRP2), + MTK_PIN(17, "EINT17", DRV_GRP2), + MTK_PIN(18, "EINT18", DRV_GRP0), + MTK_PIN(19, "EINT19", DRV_GRP0), + MTK_PIN(20, "EINT20", DRV_GRP0), + MTK_PIN(21, "EINT21", DRV_GRP2), + MTK_PIN(22, "EINT22", DRV_GRP2), + MTK_PIN(23, "EINT23", DRV_GRP2), + MTK_PIN(24, "EINT24", DRV_GRP0), + MTK_PIN(25, "EINT25", DRV_GRP0), + MTK_PIN(26, "PWRAP_SPI0_MI", DRV_GRP4), + MTK_PIN(27, "PWRAP_SPI0_MO", DRV_GRP4), + MTK_PIN(28, "PWRAP_INT", DRV_GRP4), + MTK_PIN(29, "PWRAP_SPIO0_CK", DRV_GRP4), + MTK_PIN(30, "PWARP_SPI0_CSN", DRV_GRP4), + MTK_PIN(31, "RTC32K_CK", DRV_GRP4), + MTK_PIN(32, "WATCHDOG", DRV_GRP4), + MTK_PIN(33, "SRCLKENA0", DRV_GRP4), + MTK_PIN(34, "URXD2", DRV_GRP0), + MTK_PIN(35, "UTXD2", DRV_GRP0), + MTK_PIN(36, "MRG_CLK", DRV_GRP0), + MTK_PIN(37, "MRG_SYNC", DRV_GRP0), + MTK_PIN(38, "MRG_DI", DRV_GRP0), + MTK_PIN(39, "MRG_DO", DRV_GRP0), + MTK_PIN(40, "KPROW0", DRV_GRP2), + MTK_PIN(41, "KPROW1", DRV_GRP2), + MTK_PIN(42, "KPCOL0", DRV_GRP2), + MTK_PIN(43, "KPCOL1", DRV_GRP2), + MTK_PIN(44, "JMTS", DRV_GRP2), + MTK_PIN(45, "JTCK", DRV_GRP2), + MTK_PIN(46, "JTDI", DRV_GRP2), + MTK_PIN(47, "JTDO", DRV_GRP2), + MTK_PIN(48, "SPI_CS", DRV_GRP2), + MTK_PIN(49, "SPI_CK", DRV_GRP2), + MTK_PIN(50, "SPI_MI", DRV_GRP2), + MTK_PIN(51, "SPI_MO", DRV_GRP2), + MTK_PIN(52, "SDA1", DRV_GRP2), + MTK_PIN(53, "SCL1", DRV_GRP2), + MTK_PIN(54, "DISP_PWM", DRV_GRP2), + MTK_PIN(55, "I2S_DATA_IN", DRV_GRP2), + MTK_PIN(56, "I2S_LRCK", DRV_GRP2), + MTK_PIN(57, "I2S_BCK", DRV_GRP2), + MTK_PIN(58, "SDA0", DRV_GRP2), + MTK_PIN(59, "SCL0", DRV_GRP2), + MTK_PIN(60, "SDA2", DRV_GRP2), + MTK_PIN(61, "SCL2", DRV_GRP2), + MTK_PIN(62, "URXD0", DRV_GRP2), + MTK_PIN(63, "UTXD0", DRV_GRP2), + MTK_PIN(64, "URXD1", DRV_GRP2), + MTK_PIN(65, "UTXD1", DRV_GRP2), + MTK_PIN(66, "LCM_RST", DRV_GRP2), + MTK_PIN(67, "DSI_TE", DRV_GRP2), + MTK_PIN(68, "MSDC2_CMD", DRV_GRP4), + MTK_PIN(69, "MSDC2_CLK", DRV_GRP4), + MTK_PIN(70, "MSDC2_DAT0", DRV_GRP4), + MTK_PIN(71, "MSDC2_DAT1", DRV_GRP4), + MTK_PIN(72, "MSDC2_DAT2", DRV_GRP4), + MTK_PIN(73, "MSDC2_DAT3", DRV_GRP4), + MTK_PIN(74, "TDN3", DRV_GRP0), + MTK_PIN(75, "TDP3", DRV_GRP0), + MTK_PIN(76, "TDN2", DRV_GRP0), + MTK_PIN(77, "TDP2", DRV_GRP0), + MTK_PIN(78, "TCN", DRV_GRP0), + MTK_PIN(79, "TCP", DRV_GRP0), + MTK_PIN(80, "TDN1", DRV_GRP0), + MTK_PIN(81, "TDP1", DRV_GRP0), + MTK_PIN(82, "TDN0", DRV_GRP0), + MTK_PIN(83, "TDP0", DRV_GRP0), + MTK_PIN(84, "RDN0", DRV_GRP0), + MTK_PIN(85, "RDP0", DRV_GRP0), + MTK_PIN(86, "RDN1", DRV_GRP0), + MTK_PIN(87, "RDP1", DRV_GRP0), + MTK_PIN(88, "RCN", DRV_GRP0), + MTK_PIN(89, "RCP", DRV_GRP0), + MTK_PIN(90, "RDN2", DRV_GRP0), + MTK_PIN(91, "RDP2", DRV_GRP0), + MTK_PIN(92, "RDN3", DRV_GRP0), + MTK_PIN(93, "RDP3", DRV_GRP0), + MTK_PIN(94, "RCN_A", DRV_GRP0), + MTK_PIN(95, "RCP_A", DRV_GRP0), + MTK_PIN(96, "RDN1_A", DRV_GRP0), + MTK_PIN(97, "RDP1_A", DRV_GRP0), + MTK_PIN(98, "RDN0_A", DRV_GRP0), + MTK_PIN(99, "RDP0_A", DRV_GRP0), + MTK_PIN(100, "CMDDAT0", DRV_GRP2), + MTK_PIN(101, "CMDDAT1", DRV_GRP2), + MTK_PIN(102, "CMMCLK", DRV_GRP2), + MTK_PIN(103, "CMPCLK", DRV_GRP2), + MTK_PIN(104, "MSDC1_CMD", DRV_GRP4), + MTK_PIN(105, "MSDC1_CLK", DRV_GRP4), + MTK_PIN(106, "MSDC1_DAT0", DRV_GRP4), + MTK_PIN(107, "MSDC1_DAT1", DRV_GRP4), + MTK_PIN(108, "MSDC1_DAT2", DRV_GRP4), + MTK_PIN(109, "MSDC1_DAT3", DRV_GRP4), + MTK_PIN(110, "MSDC0_DAT7", DRV_GRP4), + MTK_PIN(111, "MSDC0_DAT6", DRV_GRP4), + MTK_PIN(112, "MSDC0_DAT5", DRV_GRP4), + MTK_PIN(113, "MSDC0_DAT4", DRV_GRP4), + MTK_PIN(114, "MSDC0_RSTB", DRV_GRP4), + MTK_PIN(115, "MSDC0_CMD", DRV_GRP4), + MTK_PIN(116, "MSDC0_CLK", DRV_GRP4), + MTK_PIN(117, "MSDC0_DAT3", DRV_GRP4), + MTK_PIN(118, "MSDC0_DAT2", DRV_GRP4), + MTK_PIN(119, "MSDC0_DAT1", DRV_GRP4), + MTK_PIN(120, "MSDC0_DAT0", DRV_GRP4), +}; + +/* List all groups consisting of these pins dedicated to the enablement of + * certain hardware block and the corresponding mode for all of the pins. + * The hardware probably has multiple combinations of these pinouts. + */ + +/* UART */ +static int mt8516_uart0_0_rxd_txd_pins[] = { 62, 63, }; +static int mt8516_uart0_0_rxd_txd_funcs[] = { 1, 1, }; +static int mt8516_uart1_0_rxd_txd_pins[] = { 64, 65, }; +static int mt8516_uart1_0_rxd_txd_funcs[] = { 1, 1, }; +static int mt8516_uart2_0_rxd_txd_pins[] = { 34, 35, }; +static int mt8516_uart2_0_rxd_txd_funcs[] = { 1, 1, }; + +/* Joint those groups owning the same capability in user point of view which + * allows that people tend to use through the device tree. + */ +static const char *const mt8516_uart_groups[] = { "uart0_0_rxd_txd", + "uart1_0_rxd_txd", + "uart2_0_rxd_txd", }; + +/* MMC0 */ +static int mt8516_msdc0_pins[] = { 110, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, }; +static int mt8516_msdc0_funcs[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; + +static const struct mtk_group_desc mt8516_groups[] = { + PINCTRL_PIN_GROUP("uart0_0_rxd_txd", mt8516_uart0_0_rxd_txd), + PINCTRL_PIN_GROUP("uart1_0_rxd_txd", mt8516_uart1_0_rxd_txd), + PINCTRL_PIN_GROUP("uart2_0_rxd_txd", mt8516_uart2_0_rxd_txd), + + PINCTRL_PIN_GROUP("msdc0", mt8516_msdc0), +}; + +static const char *const mt8516_msdc_groups[] = { "msdc0" }; + +static const struct mtk_function_desc mt8516_functions[] = { + {"uart", mt8516_uart_groups, ARRAY_SIZE(mt8516_uart_groups)}, + {"msdc", mt8516_msdc_groups, ARRAY_SIZE(mt8516_msdc_groups)}, +}; + +static struct mtk_pinctrl_soc mt8516_data = { + .name = "mt8516_pinctrl", + .reg_cal = mt8516_reg_cals, + .pins = mt8516_pins, + .npins = ARRAY_SIZE(mt8516_pins), + .grps = mt8516_groups, + .ngrps = ARRAY_SIZE(mt8516_groups), + .funcs = mt8516_functions, + .nfuncs = ARRAY_SIZE(mt8516_functions), +}; + +static int mtk_pinctrl_mt8516_probe(struct udevice *dev) +{ + return mtk_pinctrl_common_probe(dev, &mt8516_data); +} + +static const struct udevice_id mt8516_pctrl_match[] = { + { .compatible = "mediatek,mt8516-pinctrl" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(mt8516_pinctrl) = { + .name = "mt8516_pinctrl", + .id = UCLASS_PINCTRL, + .of_match = mt8516_pctrl_match, + .ops = &mtk_pinctrl_ops, + .probe = mtk_pinctrl_mt8516_probe, + .priv_auto_alloc_size = sizeof(struct mtk_pinctrl_priv), +}; diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index a81e767696..6ec6f39c85 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -121,4 +121,10 @@ config RESET_SUNXI This enables support for common reset driver for Allwinner SoCs. +config RESET_HISILICON + bool "Reset controller driver for HiSilicon SoCs" + depends on DM_RESET + help + Support for reset controller on HiSilicon SoCs. + endmenu diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 4fad7d4129..7fec75bb49 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_RESET_MESON) += reset-meson.o obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o obj-$(CONFIG_RESET_MEDIATEK) += reset-mediatek.o obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o +obj-$(CONFIG_RESET_HISILICON) += reset-hisilicon.o diff --git a/drivers/reset/reset-hisilicon.c b/drivers/reset/reset-hisilicon.c new file mode 100644 index 0000000000..a9f052a0c5 --- /dev/null +++ b/drivers/reset/reset-hisilicon.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019, Linaro Limited + */ + +#include <asm/io.h> +#include <common.h> +#include <dm.h> +#include <dt-bindings/reset/ti-syscon.h> +#include <reset-uclass.h> + +struct hisi_reset_priv { + void __iomem *base; +}; + +static int hisi_reset_deassert(struct reset_ctl *rst) +{ + struct hisi_reset_priv *priv = dev_get_priv(rst->dev); + u32 val; + + val = readl(priv->base + rst->data); + if (rst->polarity & DEASSERT_SET) + val |= BIT(rst->id); + else + val &= ~BIT(rst->id); + writel(val, priv->base + rst->data); + + return 0; +} + +static int hisi_reset_assert(struct reset_ctl *rst) +{ + struct hisi_reset_priv *priv = dev_get_priv(rst->dev); + u32 val; + + val = readl(priv->base + rst->data); + if (rst->polarity & ASSERT_SET) + val |= BIT(rst->id); + else + val &= ~BIT(rst->id); + writel(val, priv->base + rst->data); + + return 0; +} + +static int hisi_reset_free(struct reset_ctl *rst) +{ + return 0; +} + +static int hisi_reset_request(struct reset_ctl *rst) +{ + return 0; +} + +static int hisi_reset_of_xlate(struct reset_ctl *rst, + struct ofnode_phandle_args *args) +{ + if (args->args_count != 3) { + debug("Invalid args_count: %d\n", args->args_count); + return -EINVAL; + } + + /* Use .data field as register offset and .id field as bit shift */ + rst->data = args->args[0]; + rst->id = args->args[1]; + rst->polarity = args->args[2]; + + return 0; +} + +static const struct reset_ops hisi_reset_reset_ops = { + .of_xlate = hisi_reset_of_xlate, + .request = hisi_reset_request, + .free = hisi_reset_free, + .rst_assert = hisi_reset_assert, + .rst_deassert = hisi_reset_deassert, +}; + +static const struct udevice_id hisi_reset_ids[] = { + { .compatible = "hisilicon,hi3798cv200-reset" }, + { } +}; + +static int hisi_reset_probe(struct udevice *dev) +{ + struct hisi_reset_priv *priv = dev_get_priv(dev); + + priv->base = dev_remap_addr(dev); + if (!priv->base) + return -ENOMEM; + + return 0; +} + +U_BOOT_DRIVER(hisi_reset) = { + .name = "hisilicon_reset", + .id = UCLASS_RESET, + .of_match = hisi_reset_ids, + .ops = &hisi_reset_reset_ops, + .probe = hisi_reset_probe, + .priv_auto_alloc_size = sizeof(struct hisi_reset_priv), +}; |