diff options
Diffstat (limited to 'drivers')
104 files changed, 8963 insertions, 376 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index 787d366933..670e0a9f4c 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -37,6 +37,7 @@ source "drivers/reset/Kconfig" source "drivers/pci/Kconfig" source "drivers/rtc/Kconfig" source "drivers/firmware/Kconfig" +source "drivers/fpga/Kconfig" source "drivers/phy/Kconfig" source "drivers/crypto/Kconfig" source "drivers/memory/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index be5b0b3b04..9958b748eb 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -34,12 +34,13 @@ obj-$(CONFIG_RESET_CONTROLLER) += reset/ obj-$(CONFIG_PCI) += pci/ obj-y += rtc/ obj-$(CONFIG_FIRMWARE) += firmware/ +obj-$(CONFIG_FPGA) += fpga/ obj-$(CONFIG_GENERIC_PHY) += phy/ obj-$(CONFIG_HAB) += hab/ obj-$(CONFIG_CRYPTO_HW) += crypto/ obj-$(CONFIG_AIODEV) += aiodev/ obj-y += memory/ -obj-y += soc/imx/ +obj-y += soc/ obj-y += nvme/ obj-y += ddr/ obj-y += power/ diff --git a/drivers/aiodev/Kconfig b/drivers/aiodev/Kconfig index 88d013aad0..03c688ae61 100644 --- a/drivers/aiodev/Kconfig +++ b/drivers/aiodev/Kconfig @@ -50,4 +50,12 @@ config STM32_ADC Support for ADC on STM32. Supports simple one-shot readings rather than continuous sampling with DMA, etc. ADC channels should be configured via device tree, using the kernel bindings. + +config ROCKCHIP_SARADC + tristate "Rockchip SARADC driver" + depends on ARCH_RK3568 || COMPILE_TEST + depends on OFDEVICE + help + Support for Successive Approximation Register (SAR) ADC in Rockchip + SoCs. endif diff --git a/drivers/aiodev/Makefile b/drivers/aiodev/Makefile index 52652f67b7..1b480f6fa3 100644 --- a/drivers/aiodev/Makefile +++ b/drivers/aiodev/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_MC13XXX_ADC) += mc13xxx_adc.o obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o obj-$(CONFIG_AM335X_ADC) += am335x_adc.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o stm32-adc-core.o +obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o diff --git a/drivers/aiodev/rockchip_saradc.c b/drivers/aiodev/rockchip_saradc.c new file mode 100644 index 0000000000..302f73c2c3 --- /dev/null +++ b/drivers/aiodev/rockchip_saradc.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021, WolfVision GmbH + * Author: Michael Riesch <michael.riesch@wolfvision.net> + * + * Originally based on the Linux kernel v5.12 drivers/iio/adc/rockchip-saradc.c. + */ + +#include <common.h> +#include <aiodev.h> +#include <linux/clk.h> +#include <regulator.h> + +#define SARADC_DATA 0x00 + +#define SARADC_CTRL 0x08 +#define SARADC_CTRL_IRQ_STATUS (1 << 6) +#define SARADC_CTRL_IRQ_ENABLE (1 << 5) +#define SARADC_CTRL_POWER_CTRL (1 << 3) +#define SARADC_CTRL_CHN_MASK 0x07 + +#define SARADC_DLY_PU_SOC 0x0c + +#define SARADC_TIMEOUT_NS (100 * MSECOND) + +struct rockchip_saradc_cfg { + unsigned int num_bits; + unsigned int num_channels; +}; + +struct rockchip_saradc_data { + const struct rockchip_saradc_cfg *config; + void __iomem *base; + struct regulator *vref; + unsigned int ref_voltage_mv; + struct clk *cclk; + struct clk *pclk; + struct aiodevice aiodev; + struct aiochannel *channels; +}; + +static inline void rockchip_saradc_reg_wr(struct rockchip_saradc_data *data, + u32 value, u32 reg) +{ + writel(value, data->base + reg); +} + +static inline u32 rockchip_saradc_reg_rd(struct rockchip_saradc_data *data, + u32 reg) +{ + return readl(data->base + reg); +} + +static int rockchip_saradc_read(struct aiochannel *chan, int *val) +{ + struct rockchip_saradc_data *data; + u32 value = 0; + u32 control = 0; + u32 mask; + u64 start; + + data = container_of(chan->aiodev, struct rockchip_saradc_data, aiodev); + + rockchip_saradc_reg_wr(data, 8, SARADC_DLY_PU_SOC); + rockchip_saradc_reg_wr(data, + (chan->index & SARADC_CTRL_CHN_MASK) | + SARADC_CTRL_IRQ_ENABLE | + SARADC_CTRL_POWER_CTRL, + SARADC_CTRL); + + start = get_time_ns(); + do { + control = rockchip_saradc_reg_rd(data, SARADC_CTRL); + + if (is_timeout(start, SARADC_TIMEOUT_NS)) + return -ETIMEDOUT; + } while (!(control & SARADC_CTRL_IRQ_STATUS)); + + mask = (1 << data->config->num_bits) - 1; + value = rockchip_saradc_reg_rd(data, SARADC_DATA) & mask; + rockchip_saradc_reg_wr(data, 0, SARADC_CTRL); + + *val = (value * data->ref_voltage_mv) / mask; + + return 0; +} + +static int rockchip_saradc_probe(struct device_d *dev) +{ + struct rockchip_saradc_data *data; + int i, ret; + + data = xzalloc(sizeof(struct rockchip_saradc_data)); + + data->config = device_get_match_data(dev); + data->aiodev.hwdev = dev; + data->aiodev.read = rockchip_saradc_read; + + data->base = dev_request_mem_region(dev, 0); + if (IS_ERR(data->base)) { + ret = PTR_ERR(data->base); + goto fail_data; + } + + data->vref = regulator_get(dev, "vref"); + if (IS_ERR(data->vref)) { + dev_err(dev, "can't get vref-supply: %pe\n", data->vref); + ret = PTR_ERR(data->vref); + goto fail_data; + } + + ret = regulator_enable(data->vref); + if (ret < 0) { + dev_err(dev, "can't enable vref-supply value: %d\n", ret); + goto fail_data; + } + + ret = regulator_get_voltage(data->vref); + if (ret < 0) { + dev_warn(dev, "can't get vref-supply value: %d\n", ret); + /* use default value as fallback */ + ret = 1800000; + } + data->ref_voltage_mv = ret / 1000; + + data->cclk = clk_get(dev, "saradc"); + if (IS_ERR(data->cclk)) { + dev_err(dev, "can't get converter clock: %pe\n", data->cclk); + ret = PTR_ERR(data->cclk); + goto fail_data; + } + + ret = clk_enable(data->cclk); + if (ret < 0) { + dev_err(dev, "can't enable converter clock: %pe\n", + ERR_PTR(ret)); + goto fail_data; + } + + data->pclk = clk_get(dev, "apb_pclk"); + if (IS_ERR(data->pclk)) { + dev_err(dev, "can't get peripheral clock: %pe\n", data->pclk); + ret = PTR_ERR(data->pclk); + goto fail_data; + } + + ret = clk_enable(data->pclk); + if (ret < 0) { + dev_err(dev, "can't enable peripheral clk: %pe\n", + ERR_PTR(ret)); + goto fail_data; + } + + data->aiodev.num_channels = data->config->num_channels; + data->channels = + xzalloc(sizeof(*data->channels) * data->aiodev.num_channels); + data->aiodev.channels = xmalloc(sizeof(*data->aiodev.channels) * + data->aiodev.num_channels); + for (i = 0; i < data->aiodev.num_channels; i++) { + data->aiodev.channels[i] = &data->channels[i]; + data->channels[i].unit = "mV"; + } + + rockchip_saradc_reg_wr(data, 0, SARADC_CTRL); + + ret = aiodevice_register(&data->aiodev); + if (ret) + goto fail_channels; + + dev_info(dev, "registered as %s\n", dev_name(&data->aiodev.dev)); + return 0; + +fail_channels: + kfree(data->channels); + kfree(data->aiodev.channels); + +fail_data: + kfree(data); + return ret; +} + +static const struct rockchip_saradc_cfg rk3568_saradc_cfg = { + .num_bits = 10, + .num_channels = 8, +}; + +static const struct of_device_id of_rockchip_saradc_match[] = { + { .compatible = "rockchip,rk3568-saradc", .data = &rk3568_saradc_cfg }, + { /* end */ } +}; + +static struct driver_d rockchip_saradc_driver = { + .name = "rockchip_saradc", + .probe = rockchip_saradc_probe, + .of_compatible = DRV_OF_COMPAT(of_rockchip_saradc_match), +}; +device_platform_driver(rockchip_saradc_driver); diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 1fa3f6c6fa..0e04ef3686 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -21,6 +21,7 @@ #include <common.h> #include <command.h> +#include <deep-probe.h> #include <driver.h> #include <malloc.h> #include <console.h> @@ -35,6 +36,12 @@ #include <pinctrl.h> #include <linux/clk/clk-conf.h> +#ifdef CONFIG_DEBUG_PROBES +#define pr_report_probe pr_info +#else +#define pr_report_probe pr_debug +#endif + LIST_HEAD(device_list); EXPORT_SYMBOL(device_list); @@ -81,8 +88,13 @@ int get_free_deviceid(const char *name_template) int device_probe(struct device_d *dev) { + static int depth = 0; int ret; + depth++; + + pr_report_probe("%*sprobe-> %s\n", depth * 4, "", dev_name(dev)); + pinctrl_select_state_default(dev); of_clk_set_defaults(dev->device_node, false); @@ -90,13 +102,21 @@ int device_probe(struct device_d *dev) ret = dev->bus->probe(dev); if (ret == 0) - return 0; + goto out; if (ret == -EPROBE_DEFER) { list_del(&dev->active); list_add(&dev->active, &deferred); - dev_dbg(dev, "probe deferred\n"); - return ret; + + /* + * -EPROBE_DEFER should never appear on a deep-probe machine so + * inform the user immediately. + */ + if (deep_probe_is_supported()) + dev_err(dev, "probe deferred\n"); + else + dev_dbg(dev, "probe deferred\n"); + goto out; } list_del(&dev->active); @@ -107,6 +127,8 @@ int device_probe(struct device_d *dev) else dev_err(dev, "probe failed: %s\n", strerror(-ret)); +out: + depth--; return ret; } diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index b0be8d1bd8..499df2fe39 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -23,3 +23,4 @@ obj-$(CONFIG_MACH_MIPS_LOONGSON)+= loongson/ obj-$(CONFIG_ARCH_LAYERSCAPE) += clk-qoric.o obj-y += analogbits/ obj-$(CONFIG_CLK_SIFIVE) += sifive/ +obj-$(CONFIG_SOC_STARFIVE) += starfive/ diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index ba726c342c..fff1e21144 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -525,6 +525,28 @@ int clk_parent_set_rate(struct clk_hw *hw, unsigned long rate, return clk_set_rate(clk_get_parent(clk), rate); } +int clk_name_set_parent(const char *clkname, const char *clkparentname) +{ + struct clk *clk = clk_lookup(clkname); + struct clk *parent = clk_lookup(clkparentname); + + if (IS_ERR(clk)) + return -ENOENT; + if (IS_ERR(parent)) + return -ENOENT; + return clk_set_parent(clk, parent); +} + +int clk_name_set_rate(const char *clkname, unsigned long rate) +{ + struct clk *clk = clk_lookup(clkname); + + if (IS_ERR(clk)) + return -ENOENT; + + return clk_set_rate(clk, rate); +} + #if defined(CONFIG_COMMON_CLK_OF_PROVIDER) /** * struct of_clk_provider - Clock provider registration structure @@ -593,6 +615,8 @@ int of_clk_add_provider(struct device_node *np, list_add(&cp->link, &of_clk_providers); pr_debug("Added clock from %s\n", np ? np->full_name : "<none>"); + of_clk_set_defaults(np, true); + return 0; } EXPORT_SYMBOL_GPL(of_clk_add_provider); @@ -619,6 +643,11 @@ struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec) { struct of_clk_provider *provider; struct clk *clk = ERR_PTR(-EPROBE_DEFER); + int ret; + + ret = of_device_ensure_probed(clkspec->np); + if (ret) + return ERR_PTR(ret); /* Check if we have such a provider in our array */ list_for_each_entry(provider, &of_clk_providers, link) { diff --git a/drivers/clk/rockchip/clk-rk3568.c b/drivers/clk/rockchip/clk-rk3568.c index 4605158500..40ab7ee3d7 100644 --- a/drivers/clk/rockchip/clk-rk3568.c +++ b/drivers/clk/rockchip/clk-rk3568.c @@ -1619,10 +1619,19 @@ static void __init rk3568_pmu_clk_init(struct device_node *np) rockchip_clk_register_branches(ctx, rk3568_clk_pmu_branches, ARRAY_SIZE(rk3568_clk_pmu_branches)); + rockchip_register_softrst(np, 1, reg_base + RK3568_PMU_SOFTRST_CON(0), + ROCKCHIP_SOFTRST_HIWORD_MASK); + rockchip_clk_protect_critical(rk3568_pmucru_critical_clocks, ARRAY_SIZE(rk3568_pmucru_critical_clocks)); rockchip_clk_of_add_provider(np, ctx); + + clk_name_set_parent("ppll", "pll_ppll"); + clk_name_set_parent("clk_rtc_32k", "clk_rtc32k_frac"); + clk_name_set_rate("clk_rtc_32k", 32768); + clk_name_set_rate("pclk_pmu", 100000000); + clk_name_set_rate("pll_ppll", 200000000); } static void __init rk3568_clk_init(struct device_node *np) @@ -1654,12 +1663,37 @@ static void __init rk3568_clk_init(struct device_node *np) rockchip_clk_register_branches(ctx, rk3568_clk_branches, ARRAY_SIZE(rk3568_clk_branches)); + rockchip_register_softrst(np, 30, reg_base + RK3568_SOFTRST_CON(0), + ROCKCHIP_SOFTRST_HIWORD_MASK); + rockchip_register_restart_notifier(ctx, RK3568_GLB_SRST_FST); rockchip_clk_protect_critical(rk3568_cru_critical_clocks, ARRAY_SIZE(rk3568_cru_critical_clocks)); rockchip_clk_of_add_provider(np, ctx); + + clk_name_set_parent("npll", "pll_npll"); + clk_name_set_parent("vpll", "pll_vpll"); + clk_name_set_parent("pclk_bus", "gpll_100m"); + clk_name_set_parent("clk_sdmmc0", "cpll_50m"); + clk_name_set_parent("cclk_emmc", "gpll_200m"); + + clk_name_set_rate("pll_cpll", 1000000000); + clk_name_set_rate("pll_gpll", 1188000000); + clk_name_set_rate("armclk", 600000000); + clk_name_set_rate("aclk_bus", 150000000); + clk_name_set_rate("pclk_bus", 100000000); + clk_name_set_rate("aclk_top_high", 300000000); + clk_name_set_rate("aclk_top_low", 200000000); + clk_name_set_rate("hclk_top", 150000000); + clk_name_set_rate("pclk_top", 100000000); + clk_name_set_rate("aclk_perimid", 300000000); + clk_name_set_rate("hclk_perimid", 150000000); + clk_name_set_rate("pll_npll", 1200000000); + clk_name_set_rate("pll_apll", 816000000); + + clk_name_set_parent("pclk_top", "gpll_100m"); } struct clk_rk3568_inits { diff --git a/drivers/clk/starfive/Makefile b/drivers/clk/starfive/Makefile new file mode 100644 index 0000000000..4e9bf7f2ba --- /dev/null +++ b/drivers/clk/starfive/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_SOC_STARFIVE_JH71XX) += jh7100-clkgen.o diff --git a/drivers/clk/starfive/clk.h b/drivers/clk/starfive/clk.h new file mode 100644 index 0000000000..cfbf116dcb --- /dev/null +++ b/drivers/clk/starfive/clk.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2021 Ahmad Fatoum, Pengutronix + */ + +#ifndef STARFIVE_CLK_H_ +#define STARFIVE_CLK_H_ + +#include <linux/clk.h> + +#define STARFIVE_CLK_ENABLE_SHIFT 31 +#define STARFIVE_CLK_INVERT_SHIFT 30 +#define STARFIVE_CLK_MUX_SHIFT 24 + +static inline struct clk *starfive_clk_underspecifid(const char *name, const char *parent) +{ + /* + * TODO With documentation available, all users of this functions can be + * migrated to one of the above or to a clk_fixed_factor with + * appropriate factor + */ + return clk_fixed_factor(name, parent, 1, 1, 0); +} + +static inline struct clk *starfive_clk_divider(const char *name, const char *parent, + void __iomem *reg, u8 width) +{ + return starfive_clk_underspecifid(name, parent); +} + +static inline struct clk *starfive_clk_gate(const char *name, const char *parent, + void __iomem *reg) +{ + return clk_gate(name, parent, reg, STARFIVE_CLK_ENABLE_SHIFT, CLK_SET_RATE_PARENT, 0); +} + +static inline struct clk *starfive_clk_divider_table(const char *name, + const char *parent, void __iomem *reg, u8 width, + const struct clk_div_table *table) +{ + return clk_divider_table(name, parent, CLK_SET_RATE_PARENT, reg, 0, + width, table, 0); +} + +static inline struct clk *starfive_clk_gated_divider(const char *name, + const char *parent, void __iomem *reg, u8 width) +{ + /* TODO divider part */ + return clk_gate(name, parent, reg, STARFIVE_CLK_ENABLE_SHIFT, CLK_SET_RATE_PARENT, 0); +} + +static inline struct clk *starfive_clk_gate_dis(const char *name, const char *parent, + void __iomem *reg) +{ + return clk_gate_inverted(name, parent, reg, STARFIVE_CLK_INVERT_SHIFT, CLK_SET_RATE_PARENT); +} + +static inline struct clk *starfive_clk_mux(const char *name, void __iomem *reg, + u8 width, const char * const *parents, u8 num_parents) +{ + return clk_mux(name, 0, reg, STARFIVE_CLK_MUX_SHIFT, width, parents, num_parents, 0); +} + +#endif diff --git a/drivers/clk/starfive/jh7100-clkgen.c b/drivers/clk/starfive/jh7100-clkgen.c new file mode 100644 index 0000000000..df5353e8e6 --- /dev/null +++ b/drivers/clk/starfive/jh7100-clkgen.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Ahmad Fatoum, Pengutronix + */ + +#include <common.h> +#include <init.h> +#include <driver.h> +#include <linux/clk.h> +#include <io.h> +#include <of.h> +#include <linux/clkdev.h> +#include <linux/err.h> +#include <dt-bindings/clock/starfive-jh7100.h> + +#include "clk.h" + + +static const char *cpundbus_root_sels[4] = { + [0] = "osc_sys", + [1] = "pll0_out", + [2] = "pll1_out", + [3] = "pll2_out", +}; + +static const char *dla_root_sels[4] = { + [0] = "osc_sys", + [1] = "pll1_out", + [2] = "pll2_out", + [3] = "dummy", +}; + +static const char *dsp_root_sels[4] = { + [0] = "osc_sys", + [1] = "pll0_out", + [2] = "pll1_out", + [3] = "pll2_out", +}; + +static const char *gmacusb_root_sels[4] = { + [0] = "osc_sys", + [1] = "pll0_out", + [2] = "pll2_out", + [3] = "dummy", +}; + +static const char *perh0_root_sels[2] = { + [0] = "osc_sys", + [1] = "pll0_out", +}; + +static const char *perh1_root_sels[2] = { + [0] = "osc_sys", + [1] = "pll2_out", +}; + +static const char *vin_root_sels[4] = { + [0] = "osc_sys", + [1] = "pll1_out", + [2] = "pll2_out", + [3] = "dummy", +}; + +static const char *vout_root_sels[4] = { + [0] = "osc_aud", + [1] = "pll0_out", + [2] = "pll2_out", + [3] = "dummy", +}; + +static const char *cdechifi4_root_sels[4] = { + [0] = "osc_sys", + [1] = "pll1_out", + [2] = "pll2_out", + [3] = "dummy", +}; + +static const char *cdec_root_sels[4] = { + [0] = "osc_sys", + [1] = "pll0_out", + [2] = "pll1_out", + [3] = "dummy", +}; + +static const char *voutbus_root_sels[4] = { + [0] = "osc_aud", + [1] = "pll0_out", + [2] = "pll2_out", + [3] = "dummy", +}; + +static const char *pll2_refclk_sels[2] = { + [0] = "osc_sys", + [1] = "osc_aud", +}; + +static const char *ddrc0_sels[4] = { + [0] = "ddrosc_div2", + [1] = "ddrpll_div2", + [2] = "ddrpll_div4", + [3] = "ddrpll_div8", +}; + +static const char *ddrc1_sels[4] = { + [0] = "ddrosc_div2", + [1] = "ddrpll_div2", + [2] = "ddrpll_div4", + [3] = "ddrpll_div8", +}; + +static const char *nne_bus_sels[2] = { + [0] = "cpu_axi", + [1] = "nnebus_src1", +}; + +static const char *usbphy_25m_sels[2] = { + [0] = "osc_sys", + [1] = "usbphy_plldiv25m", +}; + +static const char *gmac_tx_sels[4] = { + [0] = "gmac_gtxclk", + [1] = "gmac_mii_txclk", + [2] = "gmac_rmii_txclk", + [3] = "dummy", +}; + +static const char *gmac_rx_pre_sels[2] = { + [0] = "gmac_gr_mii_rxclk", + [1] = "gmac_rmii_rxclk", +}; + +static struct clk *clks[CLK_END]; + +/* assume osc_sys as direct parent for clocks of yet unknown lineage */ +#define UNKNOWN "osc_sys" + +static void starfive_clkgen_init(struct device_node *np, void __iomem *base) +{ + clks[CLK_OSC_SYS] = of_clk_get_by_name(np, "osc_sys"); + clks[CLK_OSC_AUD] = of_clk_get_by_name(np, "osc_aud"); + clks[CLK_PLL0_OUT] = starfive_clk_underspecifid("pll0_out", "osc_sys"); + clks[CLK_PLL1_OUT] = starfive_clk_underspecifid("pll1_out", "osc_sys"); + clks[CLK_PLL2_OUT] = starfive_clk_underspecifid("pll2_out", "pll2_refclk"); + clks[CLK_CPUNDBUS_ROOT] = starfive_clk_mux("cpundbus_root", base + 0x0, 2, cpundbus_root_sels, ARRAY_SIZE(cpundbus_root_sels)); + clks[CLK_DLA_ROOT] = starfive_clk_mux("dla_root", base + 0x4, 2, dla_root_sels, ARRAY_SIZE(dla_root_sels)); + clks[CLK_DSP_ROOT] = starfive_clk_mux("dsp_root", base + 0x8, 2, dsp_root_sels, ARRAY_SIZE(dsp_root_sels)); + clks[CLK_GMACUSB_ROOT] = starfive_clk_mux("gmacusb_root", base + 0xC, 2, gmacusb_root_sels, ARRAY_SIZE(gmacusb_root_sels)); + clks[CLK_PERH0_ROOT] = starfive_clk_mux("perh0_root", base + 0x10, 1, perh0_root_sels, ARRAY_SIZE(perh0_root_sels)); + clks[CLK_PERH1_ROOT] = starfive_clk_mux("perh1_root", base + 0x14, 1, perh1_root_sels, ARRAY_SIZE(perh1_root_sels)); + clks[CLK_VIN_ROOT] = starfive_clk_mux("vin_root", base + 0x18, 2, vin_root_sels, ARRAY_SIZE(vin_root_sels)); + clks[CLK_VOUT_ROOT] = starfive_clk_mux("vout_root", base + 0x1C, 2, vout_root_sels, ARRAY_SIZE(vout_root_sels)); + clks[CLK_AUDIO_ROOT] = starfive_clk_gated_divider("audio_root", UNKNOWN, base + 0x20, 4); + clks[CLK_CDECHIFI4_ROOT] = starfive_clk_mux("cdechifi4_root", base + 0x24, 2, cdechifi4_root_sels, ARRAY_SIZE(cdechifi4_root_sels)); + clks[CLK_CDEC_ROOT] = starfive_clk_mux("cdec_root", base + 0x28, 2, cdec_root_sels, ARRAY_SIZE(cdec_root_sels)); + clks[CLK_VOUTBUS_ROOT] = starfive_clk_mux("voutbus_root", base + 0x2C, 2, voutbus_root_sels, ARRAY_SIZE(voutbus_root_sels)); + clks[CLK_CPUNBUS_ROOT_DIV] = starfive_clk_divider("cpunbus_root_div", "cpunbus_root", base + 0x30, 2); + clks[CLK_DSP_ROOT_DIV] = starfive_clk_divider("dsp_root_div", "dsp_root", base + 0x34, 3); + clks[CLK_PERH0_SRC] = starfive_clk_divider("perh0_src", "perh0_root", base + 0x38, 3); + clks[CLK_PERH1_SRC] = starfive_clk_divider("perh1_src", "perh1_root", base + 0x3C, 3); + clks[CLK_PLL0_TESTOUT] = starfive_clk_gated_divider("pll0_testout", "pll0_out", base + 0x40, 5); + clks[CLK_PLL1_TESTOUT] = starfive_clk_gated_divider("pll1_testout", "pll1_out", base + 0x44, 5); + clks[CLK_PLL2_TESTOUT] = starfive_clk_gated_divider("pll2_testout", "pll2_out", base + 0x48, 5); + clks[CLK_PLL2_REF] = starfive_clk_mux("pll2_refclk", base + 0x4C, 1, pll2_refclk_sels, ARRAY_SIZE(pll2_refclk_sels)); + clks[CLK_CPU_CORE] = starfive_clk_divider("cpu_core", UNKNOWN, base + 0x50, 4); + clks[CLK_CPU_AXI] = starfive_clk_divider("cpu_axi", UNKNOWN, base + 0x54, 4); + clks[CLK_AHB_BUS] = starfive_clk_divider("ahb_bus", UNKNOWN, base + 0x58, 4); + clks[CLK_APB1_BUS] = starfive_clk_divider("apb1_bus", UNKNOWN, base + 0x5C, 4); + clks[CLK_APB2_BUS] = starfive_clk_divider("apb2_bus", UNKNOWN, base + 0x60, 4); + clks[CLK_DOM3AHB_BUS] = starfive_clk_gate("dom3ahb_bus", UNKNOWN, base + 0x64); + clks[CLK_DOM7AHB_BUS] = starfive_clk_gate("dom7ahb_bus", UNKNOWN, base + 0x68); + clks[CLK_U74_CORE0] = starfive_clk_gate("u74_core0", UNKNOWN, base + 0x6C); + clks[CLK_U74_CORE1] = starfive_clk_gated_divider("u74_core1", "", base + 0x70, 4); + clks[CLK_U74_AXI] = starfive_clk_gate("u74_axi", UNKNOWN, base + 0x74); + clks[CLK_U74RTC_TOGGLE] = starfive_clk_gate("u74rtc_toggle", UNKNOWN, base + 0x78); + clks[CLK_SGDMA2P_AXI] = starfive_clk_gate("sgdma2p_axi", UNKNOWN, base + 0x7C); + clks[CLK_DMA2PNOC_AXI] = starfive_clk_gate("dma2pnoc_axi", UNKNOWN, base + 0x80); + clks[CLK_SGDMA2P_AHB] = starfive_clk_gate("sgdma2p_ahb", UNKNOWN, base + 0x84); + clks[CLK_DLA_BUS] = starfive_clk_divider("dla_bus", UNKNOWN, base + 0x88, 3); + clks[CLK_DLA_AXI] = starfive_clk_gate("dla_axi", UNKNOWN, base + 0x8C); + clks[CLK_DLANOC_AXI] = starfive_clk_gate("dlanoc_axi", UNKNOWN, base + 0x90); + clks[CLK_DLA_APB] = starfive_clk_gate("dla_apb", UNKNOWN, base + 0x94); + clks[CLK_VP6_CORE] = starfive_clk_gated_divider("vp6_core", UNKNOWN, base + 0x98, 3); + clks[CLK_VP6BUS_SRC] = starfive_clk_divider("vp6bus_src", UNKNOWN, base + 0x9C, 3); + clks[CLK_VP6_AXI] = starfive_clk_gated_divider("vp6_axi", UNKNOWN, base + 0xA0, 3); + clks[CLK_VCDECBUS_SRC] = starfive_clk_divider("vcdecbus_src", UNKNOWN, base + 0xA4, 3); + clks[CLK_VDEC_BUS] = starfive_clk_divider("vdec_bus", UNKNOWN, base + 0xA8, 4); + clks[CLK_VDEC_AXI] = starfive_clk_gate("vdec_axi", UNKNOWN, base + 0xAC); + clks[CLK_VDECBRG_MAIN] = starfive_clk_gate("vdecbrg_mainclk", UNKNOWN, base + 0xB0); + clks[CLK_VDEC_BCLK] = starfive_clk_gated_divider("vdec_bclk", UNKNOWN, base + 0xB4, 4); + clks[CLK_VDEC_CCLK] = starfive_clk_gated_divider("vdec_cclk", UNKNOWN, base + 0xB8, 4); + clks[CLK_VDEC_APB] = starfive_clk_gate("vdec_apb", UNKNOWN, base + 0xBC); + clks[CLK_JPEG_AXI] = starfive_clk_gated_divider("jpeg_axi", UNKNOWN, base + 0xC0, 4); + clks[CLK_JPEG_CCLK] = starfive_clk_gated_divider("jpeg_cclk", UNKNOWN, base + 0xC4, 4); + clks[CLK_JPEG_APB] = starfive_clk_gate("jpeg_apb", UNKNOWN, base + 0xC8); + clks[CLK_GC300_2X] = starfive_clk_gated_divider("gc300_2x", UNKNOWN, base + 0xCC, 4); + clks[CLK_GC300_AHB] = starfive_clk_gate("gc300_ahb", UNKNOWN, base + 0xD0); + clks[CLK_JPCGC300_AXIBUS] = starfive_clk_divider("jpcgc300_axibus", UNKNOWN, base + 0xD4, 4); + clks[CLK_GC300_AXI] = starfive_clk_gate("gc300_axi", UNKNOWN, base + 0xD8); + clks[CLK_JPCGC300_MAIN] = starfive_clk_gate("jpcgc300_mainclk", UNKNOWN, base + 0xDC); + clks[CLK_VENC_BUS] = starfive_clk_divider("venc_bus", UNKNOWN, base + 0xE0, 4); + clks[CLK_VENC_AXI] = starfive_clk_gate("venc_axi", UNKNOWN, base + 0xE4); + clks[CLK_VENCBRG_MAIN] = starfive_clk_gate("vencbrg_mainclk", UNKNOWN, base + 0xE8); + clks[CLK_VENC_BCLK] = starfive_clk_gated_divider("venc_bclk", UNKNOWN, base + 0xEC, 4); + clks[CLK_VENC_CCLK] = starfive_clk_gated_divider("venc_cclk", UNKNOWN, base + 0xF0, 4); + clks[CLK_VENC_APB] = starfive_clk_gate("venc_apb", UNKNOWN, base + 0xF4); + clks[CLK_DDRPLL_DIV2] = starfive_clk_gated_divider("ddrpll_div2", UNKNOWN, base + 0xF8, 2); + clks[CLK_DDRPLL_DIV4] = starfive_clk_gated_divider("ddrpll_div4", UNKNOWN, base + 0xFC, 2); + clks[CLK_DDRPLL_DIV8] = starfive_clk_gated_divider("ddrpll_div8", UNKNOWN, base + 0x100, 2); + clks[CLK_DDROSC_DIV2] = starfive_clk_gated_divider("ddrosc_div2", UNKNOWN, base + 0x104, 2); + clks[CLK_DDRC0] = starfive_clk_mux("ddrc0", base + 0x108, 2, ddrc0_sels, ARRAY_SIZE(ddrc0_sels)); + clks[CLK_DDRC1] = starfive_clk_mux("ddrc1", base + 0x10C, 2, ddrc1_sels, ARRAY_SIZE(ddrc1_sels)); + clks[CLK_DDRPHY_APB] = starfive_clk_gate("ddrphy_apb", UNKNOWN, base + 0x110); + clks[CLK_NOC_ROB] = starfive_clk_divider("noc_rob", UNKNOWN, base + 0x114, 4); + clks[CLK_NOC_COG] = starfive_clk_divider("noc_cog", UNKNOWN, base + 0x118, 4); + clks[CLK_NNE_AHB] = starfive_clk_gate("nne_ahb", UNKNOWN, base + 0x11C); + clks[CLK_NNEBUS_SRC1] = starfive_clk_divider("nnebus_src1", UNKNOWN, base + 0x120, 3); + clks[CLK_NNE_BUS] = starfive_clk_mux("nne_bus", base + 0x124, 2, nne_bus_sels, ARRAY_SIZE(nne_bus_sels)); + clks[CLK_NNE_AXI] = starfive_clk_gate("nne_axi", UNKNOWN, base + 0x128); + clks[CLK_NNENOC_AXI] = starfive_clk_gate("nnenoc_axi", UNKNOWN, base + 0x12C); + clks[CLK_DLASLV_AXI] = starfive_clk_gate("dlaslv_axi", UNKNOWN, base + 0x130); + clks[CLK_DSPX2C_AXI] = starfive_clk_gate("dspx2c_axi", UNKNOWN, base + 0x134); + clks[CLK_HIFI4_SRC] = starfive_clk_divider("hifi4_src", UNKNOWN, base + 0x138, 3); + clks[CLK_HIFI4_COREFREE] = starfive_clk_divider("hifi4_corefree", UNKNOWN, base + 0x13C, 4); + clks[CLK_HIFI4_CORE] = starfive_clk_gate("hifi4_core", UNKNOWN, base + 0x140); + clks[CLK_HIFI4_BUS] = starfive_clk_divider("hifi4_bus", UNKNOWN, base + 0x144, 4); + clks[CLK_HIFI4_AXI] = starfive_clk_gate("hifi4_axi", UNKNOWN, base + 0x148); + clks[CLK_HIFI4NOC_AXI] = starfive_clk_gate("hifi4noc_axi", UNKNOWN, base + 0x14C); + clks[CLK_SGDMA1P_BUS] = starfive_clk_divider("sgdma1p_bus", UNKNOWN, base + 0x150, 4); + clks[CLK_SGDMA1P_AXI] = starfive_clk_gate("sgdma1p_axi", UNKNOWN, base + 0x154); + clks[CLK_DMA1P_AXI] = starfive_clk_gate("dma1p_axi", UNKNOWN, base + 0x158); + clks[CLK_X2C_AXI] = starfive_clk_gated_divider("x2c_axi", UNKNOWN, base + 0x15C, 4); + clks[CLK_USB_BUS] = starfive_clk_divider("usb_bus", UNKNOWN, base + 0x160, 4); + clks[CLK_USB_AXI] = starfive_clk_gate("usb_axi", UNKNOWN, base + 0x164); + clks[CLK_USBNOC_AXI] = starfive_clk_gate("usbnoc_axi", UNKNOWN, base + 0x168); + clks[CLK_USBPHY_ROOTDIV] = starfive_clk_divider("usbphy_rootdiv", UNKNOWN, base + 0x16C, 3); + clks[CLK_USBPHY_125M] = starfive_clk_gated_divider("usbphy_125m", UNKNOWN, base + 0x170, 4); + clks[CLK_USBPHY_PLLDIV25M] = starfive_clk_gated_divider("usbphy_plldiv25m", UNKNOWN, base + 0x174, 6); + clks[CLK_USBPHY_25M] = starfive_clk_mux("usbphy_25m", base + 0x178, 1, usbphy_25m_sels, ARRAY_SIZE(usbphy_25m_sels)); + clks[CLK_AUDIO_DIV] = starfive_clk_divider("audio_div", UNKNOWN, base + 0x17C, 18); + clks[CLK_AUDIO_SRC] = starfive_clk_gate("audio_src", UNKNOWN, base + 0x180); + clks[CLK_AUDIO_12288] = starfive_clk_gate("audio_12288", UNKNOWN, base + 0x184); + clks[CLK_VIN_SRC] = starfive_clk_gated_divider("vin_src", UNKNOWN, base + 0x188, 3); + clks[CLK_ISP0_BUS] = starfive_clk_divider("isp0_bus", UNKNOWN, base + 0x18C, 4); + clks[CLK_ISP0_AXI] = starfive_clk_gate("isp0_axi", UNKNOWN, base + 0x190); + clks[CLK_ISP0NOC_AXI] = starfive_clk_gate("isp0noc_axi", UNKNOWN, base + 0x194); + clks[CLK_ISPSLV_AXI] = starfive_clk_gate("ispslv_axi", UNKNOWN, base + 0x198); + clks[CLK_ISP1_BUS] = starfive_clk_divider("isp1_bus", UNKNOWN, base + 0x19C, 4); + clks[CLK_ISP1_AXI] = starfive_clk_gate("isp1_axi", UNKNOWN, base + 0x1A0); + clks[CLK_ISP1NOC_AXI] = starfive_clk_gate("isp1noc_axi", UNKNOWN, base + 0x1A4); + clks[CLK_VIN_BUS] = starfive_clk_divider("vin_bus", UNKNOWN, base + 0x1A8, 4); + clks[CLK_VIN_AXI] = starfive_clk_gate("vin_axi", UNKNOWN, base + 0x1AC); + clks[CLK_VINNOC_AXI] = starfive_clk_gate("vinnoc_axi", UNKNOWN, base + 0x1B0); + clks[CLK_VOUT_SRC] = starfive_clk_gated_divider("vout_src", UNKNOWN, base + 0x1B4, 3); + clks[CLK_DISPBUS_SRC] = starfive_clk_divider("dispbus_src", UNKNOWN, base + 0x1B8, 3); + clks[CLK_DISP_BUS] = starfive_clk_divider("disp_bus", UNKNOWN, base + 0x1BC, 3); + clks[CLK_DISP_AXI] = starfive_clk_gate("disp_axi", UNKNOWN, base + 0x1C0); + clks[CLK_DISPNOC_AXI] = starfive_clk_gate("dispnoc_axi", UNKNOWN, base + 0x1C4); + clks[CLK_SDIO0_AHB] = starfive_clk_gate("sdio0_ahb", UNKNOWN, base + 0x1C8); + clks[CLK_SDIO0_CCLKINT] = starfive_clk_gated_divider("sdio0_cclkint", UNKNOWN, base + 0x1CC, 5); + clks[CLK_SDIO0_CCLKINT_INV] = starfive_clk_gate_dis("sdio0_cclkint_inv", UNKNOWN, base + 0x1D0); + clks[CLK_SDIO1_AHB] = starfive_clk_gate("sdio1_ahb", UNKNOWN, base + 0x1D4); + clks[CLK_SDIO1_CCLKINT] = starfive_clk_gated_divider("sdio1_cclkint", UNKNOWN, base + 0x1D8, 5); + clks[CLK_SDIO1_CCLKINT_INV] = starfive_clk_gate_dis("sdio1_cclkint_inv", UNKNOWN, base + 0x1DC); + clks[CLK_GMAC_AHB] = starfive_clk_gate("gmac_ahb", UNKNOWN, base + 0x1E0); + clks[CLK_GMAC_ROOT_DIV] = starfive_clk_divider("gmac_root_div", UNKNOWN, base + 0x1E4, 4); + clks[CLK_GMAC_PTP_REF] = starfive_clk_gated_divider("gmac_ptp_refclk", UNKNOWN, base + 0x1E8, 5); + clks[CLK_GMAC_GTX] = starfive_clk_gated_divider("gmac_gtxclk", UNKNOWN, base + 0x1EC, 8); + clks[CLK_GMAC_RMII_TX] = starfive_clk_gated_divider("gmac_rmii_txclk", UNKNOWN, base + 0x1F0, 4); + clks[CLK_GMAC_RMII_RX] = starfive_clk_gated_divider("gmac_rmii_rxclk", UNKNOWN, base + 0x1F4, 4); + clks[CLK_GMAC_TX] = starfive_clk_mux("gmac_tx", base + 0x1F8, 2, gmac_tx_sels, ARRAY_SIZE(gmac_tx_sels)); + clks[CLK_GMAC_TX_INV] = starfive_clk_gate_dis("gmac_tx_inv", UNKNOWN, base + 0x1FC); + clks[CLK_GMAC_RX_PRE] = starfive_clk_mux("gmac_rx_pre", base + 0x200, 1, gmac_rx_pre_sels, ARRAY_SIZE(gmac_rx_pre_sels)); + clks[CLK_GMAC_RX_INV] = starfive_clk_gate_dis("gmac_rx_inv", UNKNOWN, base + 0x204); + clks[CLK_GMAC_RMII] = starfive_clk_gate("gmac_rmii", UNKNOWN, base + 0x208); + clks[CLK_GMAC_TOPHYREF] = starfive_clk_gated_divider("gmac_tophyref", UNKNOWN, base + 0x20C, 7); + clks[CLK_SPI2AHB_AHB] = starfive_clk_gate("spi2ahb_ahb", UNKNOWN, base + 0x210); + clks[CLK_SPI2AHB_CORE] = starfive_clk_gated_divider("spi2ahb_core", UNKNOWN, base + 0x214, 5); + clks[CLK_EZMASTER_AHB] = starfive_clk_gate("ezmaster_ahb", UNKNOWN, base + 0x218); + clks[CLK_E24_AHB] = starfive_clk_gate("e24_ahb", UNKNOWN, base + 0x21C); + clks[CLK_E24RTC_TOGGLE] = starfive_clk_gate("e24rtc_toggle", UNKNOWN, base + 0x220); + clks[CLK_QSPI_AHB] = starfive_clk_gate("qspi_ahb", UNKNOWN, base + 0x224); + clks[CLK_QSPI_APB] = starfive_clk_gate("qspi_apb", UNKNOWN, base + 0x228); + clks[CLK_QSPI_REF] = starfive_clk_gated_divider("qspi_refclk", UNKNOWN, base + 0x22C, 5); + clks[CLK_SEC_AHB] = starfive_clk_gate("sec_ahb", UNKNOWN, base + 0x230); + clks[CLK_AES] = starfive_clk_gate("aes_clk", UNKNOWN, base + 0x234); + clks[CLK_SHA] = starfive_clk_gate("sha_clk", UNKNOWN, base + 0x238); + clks[CLK_PKA] = starfive_clk_gate("pka_clk", UNKNOWN, base + 0x23C); + clks[CLK_TRNG_APB] = starfive_clk_gate("trng_apb", UNKNOWN, base + 0x240); + clks[CLK_OTP_APB] = starfive_clk_gate("otp_apb", UNKNOWN, base + 0x244); + clks[CLK_UART0_APB] = starfive_clk_gate("uart0_apb", UNKNOWN, base + 0x248); + clks[CLK_UART0_CORE] = starfive_clk_gated_divider("uart0_core", UNKNOWN, base + 0x24C, 6); + clks[CLK_UART1_APB] = starfive_clk_gate("uart1_apb", UNKNOWN, base + 0x250); + clks[CLK_UART1_CORE] = starfive_clk_gated_divider("uart1_core", UNKNOWN, base + 0x254, 6); + clks[CLK_SPI0_APB] = starfive_clk_gate("spi0_apb", UNKNOWN, base + 0x258); + clks[CLK_SPI0_CORE] = starfive_clk_gated_divider("spi0_core", UNKNOWN, base + 0x25C, 6); + clks[CLK_SPI1_APB] = starfive_clk_gate("spi1_apb", UNKNOWN, base + 0x260); + clks[CLK_SPI1_CORE] = starfive_clk_gated_divider("spi1_core", UNKNOWN, base + 0x264, 6); + clks[CLK_I2C0_APB] = starfive_clk_gate("i2c0_apb", UNKNOWN, base + 0x268); + clks[CLK_I2C0_CORE] = starfive_clk_gated_divider("i2c0_core", UNKNOWN, base + 0x26C, 6); + clks[CLK_I2C1_APB] = starfive_clk_gate("i2c1_apb", UNKNOWN, base + 0x270); + clks[CLK_I2C1_CORE] = starfive_clk_gated_divider("i2c1_core", UNKNOWN, base + 0x274, 6); + clks[CLK_GPIO_APB] = starfive_clk_gate("gpio_apb", UNKNOWN, base + 0x278); + clks[CLK_UART2_APB] = starfive_clk_gate("uart2_apb", UNKNOWN, base + 0x27C); + clks[CLK_UART2_CORE] = starfive_clk_gated_divider("uart2_core", UNKNOWN, base + 0x280, 6); + clks[CLK_UART3_APB] = starfive_clk_gate("uart3_apb", UNKNOWN, base + 0x284); + clks[CLK_UART3_CORE] = starfive_clk_gated_divider("uart3_core", UNKNOWN, base + 0x288, 6); + clks[CLK_SPI2_APB] = starfive_clk_gate("spi2_apb", UNKNOWN, base + 0x28C); + clks[CLK_SPI2_CORE] = starfive_clk_gated_divider("spi2_core", UNKNOWN, base + 0x290, 6); + clks[CLK_SPI3_APB] = starfive_clk_gate("spi3_apb", UNKNOWN, base + 0x294); + clks[CLK_SPI3_CORE] = starfive_clk_gated_divider("spi3_core", UNKNOWN, base + 0x298, 6); + clks[CLK_I2C2_APB] = starfive_clk_gate("i2c2_apb", UNKNOWN, base + 0x29C); + clks[CLK_I2C2_CORE] = starfive_clk_gated_divider("i2c2_core", UNKNOWN, base + 0x2A0, 6); + clks[CLK_I2C3_APB] = starfive_clk_gate("i2c3_apb", UNKNOWN, base + 0x2A4); + clks[CLK_I2C3_CORE] = starfive_clk_gated_divider("i2c3_core", UNKNOWN, base + 0x2A8, 6); + clks[CLK_WDTIMER_APB] = starfive_clk_gate("wdtimer_apb", UNKNOWN, base + 0x2AC); + clks[CLK_WDT_CORE] = starfive_clk_gated_divider("wdt_coreclk", UNKNOWN, base + 0x2B0, 6); + clks[CLK_TIMER0_CORE] = starfive_clk_gated_divider("timer0_coreclk", UNKNOWN, base + 0x2B4, 6); + clks[CLK_TIMER1_CORE] = starfive_clk_gated_divider("timer1_coreclk", UNKNOWN, base + 0x2B8, 6); + clks[CLK_TIMER2_CORE] = starfive_clk_gated_divider("timer2_coreclk", UNKNOWN, base + 0x2BC, 6); + clks[CLK_TIMER3_CORE] = starfive_clk_gated_divider("timer3_coreclk", UNKNOWN, base + 0x2C0, 6); + clks[CLK_TIMER4_CORE] = starfive_clk_gated_divider("timer4_coreclk", UNKNOWN, base + 0x2C4, 6); + clks[CLK_TIMER5_CORE] = starfive_clk_gated_divider("timer5_coreclk", UNKNOWN, base + 0x2C8, 6); + clks[CLK_TIMER6_CORE] = starfive_clk_gated_divider("timer6_coreclk", UNKNOWN, base + 0x2CC, 6); + clks[CLK_VP6INTC_APB] = starfive_clk_gate("vp6intc_apb", UNKNOWN, base + 0x2D0); + clks[CLK_PWM_APB] = starfive_clk_gate("pwm_apb", UNKNOWN, base + 0x2D4); + clks[CLK_MSI_APB] = starfive_clk_gate("msi_apb", UNKNOWN, base + 0x2D8); + clks[CLK_TEMP_APB] = starfive_clk_gate("temp_apb", UNKNOWN, base + 0x2DC); + clks[CLK_TEMP_SENSE] = starfive_clk_gated_divider("temp_sense", UNKNOWN, base + 0x2E0, 5); + clks[CLK_SYSERR_APB] = starfive_clk_gate("syserr_apb", UNKNOWN, base + 0x2E4); +} + +static struct clk_onecell_data clk_data; + +static int starfive_clkgen_clk_probe(struct device_d *dev) +{ + struct resource *iores; + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + + starfive_clkgen_init(dev->device_node, IOMEM(iores->start)); + + clk_data.clks = clks; + clk_data.clk_num = ARRAY_SIZE(clks); + of_clk_add_provider(dev->device_node, of_clk_src_onecell_get, + &clk_data); + + return 0; +} + +static __maybe_unused struct of_device_id starfive_clkgen_clk_dt_ids[] = { + { .compatible = "starfive,jh7100-clkgen" }, + { /* sentinel */ } +}; + +static struct driver_d starfive_clkgen_clk_driver = { + .probe = starfive_clkgen_clk_probe, + .name = "starfive-clkgen", + .of_compatible = starfive_clkgen_clk_dt_ids, +}; +core_platform_driver(starfive_clkgen_clk_driver); diff --git a/drivers/clocksource/timer-clint.c b/drivers/clocksource/timer-clint.c index b7360010bd..412ce3e1e9 100644 --- a/drivers/clocksource/timer-clint.c +++ b/drivers/clocksource/timer-clint.c @@ -17,6 +17,7 @@ #include <linux/err.h> #include <io.h> #include <asm/timer.h> +#include <asm/system.h> #define CLINT_TIMER_VAL_OFF 0xbff8 @@ -63,8 +64,8 @@ static int clint_timer_init_dt(struct device_d* dev) { struct resource *iores; - /* one timer is enough */ - if (clint_timer_val) + /* one timer is enough. Only M-Mode */ + if (clint_timer_val || riscv_mode() != RISCV_M_MODE) return 0; iores = dev_request_mem_resource(dev, 0); @@ -72,7 +73,7 @@ static int clint_timer_init_dt(struct device_d* dev) return PTR_ERR(iores); clint_timer_val = IOMEM(iores->start) + CLINT_TIMER_VAL_OFF; - dev_info(dev, "running at %lu Hz\n", riscv_timebase); + dev_dbg(dev, "running at %lu Hz\n", riscv_timebase); clint_clocksource.mult = clocksource_hz2mult(riscv_timebase, clint_clocksource.shift); diff --git a/drivers/clocksource/timer-riscv.c b/drivers/clocksource/timer-riscv.c index ef67cff475..5a517fe6b4 100644 --- a/drivers/clocksource/timer-riscv.c +++ b/drivers/clocksource/timer-riscv.c @@ -12,6 +12,7 @@ #include <clock.h> #include <asm/timer.h> #include <asm/csr.h> +#include <asm/system.h> static u64 notrace riscv_timer_get_count_sbi(void) { @@ -45,7 +46,7 @@ static u64 notrace riscv_timer_get_count_rdcycle(void) static u64 notrace riscv_timer_get_count(void) { - if (IS_ENABLED(CONFIG_RISCV_SBI)) + if (riscv_mode() == RISCV_S_MODE) return riscv_timer_get_count_sbi(); else return riscv_timer_get_count_rdcycle(); @@ -59,7 +60,7 @@ static struct clocksource riscv_clocksource = { static int riscv_timer_init(struct device_d* dev) { - dev_info(dev, "running at %lu Hz\n", riscv_timebase); + dev_dbg(dev, "running at %lu Hz\n", riscv_timebase); riscv_clocksource.mult = clocksource_hz2mult(riscv_timebase, riscv_clocksource.shift); diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index b162b08b0a..bbd2bcda97 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -1,3 +1,3 @@ obj-$(CONFIG_FIRMWARE_ALTERA_SERIAL) += altera_serial.o -obj-$(CONFIG_FIRMWARE_ALTERA_SOCFPGA) += socfpga.o +obj-$(CONFIG_FIRMWARE_ALTERA_SOCFPGA) += socfpga.o socfpga_sdr.o obj-$(CONFIG_FIRMWARE_ZYNQMP_FPGA) += zynqmp-fpga.o diff --git a/drivers/firmware/altera_serial.c b/drivers/firmware/altera_serial.c index 3a0175dd07..49460c6a70 100644 --- a/drivers/firmware/altera_serial.c +++ b/drivers/firmware/altera_serial.c @@ -371,6 +371,7 @@ static int altera_spi_probe(struct device_d *dev) if (model) fh->model = xstrdup(model); fh->dev = dev; + fh->device_node = dev->device_node; this->spi = (struct spi_device *)dev->type_data; this->data = data; diff --git a/drivers/firmware/socfpga.c b/drivers/firmware/socfpga.c index 234fb2d094..dd9202bf2b 100644 --- a/drivers/firmware/socfpga.c +++ b/drivers/firmware/socfpga.c @@ -27,6 +27,7 @@ */ #include <firmware.h> +#include <fpga-mgr.h> #include <command.h> #include <common.h> #include <malloc.h> @@ -38,6 +39,9 @@ #include <mach/cyclone5-reset-manager.h> #include <mach/cyclone5-regs.h> #include <mach/cyclone5-sdram.h> +#include <asm/fncpy.h> +#include <mmu.h> +#include <asm/cache.h> #define FPGAMGRREGS_STAT 0x0 #define FPGAMGRREGS_CTRL 0x4 @@ -77,21 +81,17 @@ #define CDRATIO_x4 0x2 #define CDRATIO_x8 0x3 -struct fpgamgr { - struct firmware_handler fh; - struct device_d dev; - void __iomem *regs; - void __iomem *regs_data; - int programmed; -}; +extern void socfpga_sdram_apply_static_cfg(void __iomem *sdrctrlgrp); +extern void socfpga_sdram_apply_static_cfg_end(void *); +extern const u32 socfpga_sdram_apply_static_cfg_sz; /* Get the FPGA mode */ -static uint32_t fpgamgr_get_mode(struct fpgamgr *mgr) +static uint32_t socfpga_fpgamgr_get_mode(struct fpgamgr *mgr) { return readl(mgr->regs + FPGAMGRREGS_STAT) & FPGAMGRREGS_STAT_MODE_MASK; } -static int fpgamgr_dclkcnt_set(struct fpgamgr *mgr, unsigned long cnt) +static int socfpga_fpgamgr_dclkcnt_set(struct fpgamgr *mgr, unsigned long cnt) { uint64_t start; @@ -115,7 +115,7 @@ static int fpgamgr_dclkcnt_set(struct fpgamgr *mgr, unsigned long cnt) } /* Start the FPGA programming by initialize the FPGA Manager */ -static int fpgamgr_program_init(struct fpgamgr *mgr) +static int socfpga_fpgamgr_program_init(struct fpgamgr *mgr) { unsigned long reg; uint32_t ctrl = 0, ratio; @@ -164,7 +164,7 @@ static int fpgamgr_program_init(struct fpgamgr *mgr) /* (1) wait until FPGA enter reset phase */ start = get_time_ns(); while (1) { - if (fpgamgr_get_mode(mgr) == FPGAMGRREGS_MODE_RESETPHASE) + if (socfpga_fpgamgr_get_mode(mgr) == FPGAMGRREGS_MODE_RESETPHASE) break; if (is_timeout(start, 100 * MSECOND)) return -ETIMEDOUT; @@ -178,7 +178,7 @@ static int fpgamgr_program_init(struct fpgamgr *mgr) /* (2) wait until FPGA enter configuration phase */ start = get_time_ns(); while (1) { - if (fpgamgr_get_mode(mgr) == FPGAMGRREGS_MODE_CFGPHASE) + if (socfpga_fpgamgr_get_mode(mgr) == FPGAMGRREGS_MODE_CFGPHASE) break; if (is_timeout(start, 100 * MSECOND)) return -ETIMEDOUT; @@ -196,7 +196,7 @@ static int fpgamgr_program_init(struct fpgamgr *mgr) } /* Ensure the FPGA entering config done */ -static int fpgamgr_program_poll_cd(struct fpgamgr *mgr) +static int socfpga_fpgamgr_program_poll_cd(struct fpgamgr *mgr) { unsigned long reg; uint32_t val; @@ -230,18 +230,18 @@ static int fpgamgr_program_poll_cd(struct fpgamgr *mgr) } /* Ensure the FPGA entering init phase */ -static int fpgamgr_program_poll_initphase(struct fpgamgr *mgr) +static int socfpga_fpgamgr_program_poll_initphase(struct fpgamgr *mgr) { uint64_t start; /* additional clocks for the CB to enter initialization phase */ - if (fpgamgr_dclkcnt_set(mgr, 0x4) != 0) + if (socfpga_fpgamgr_dclkcnt_set(mgr, 0x4) != 0) return -5; /* (4) wait until FPGA enter init phase or user mode */ start = get_time_ns(); while (1) { - int mode = fpgamgr_get_mode(mgr); + int mode = socfpga_fpgamgr_get_mode(mgr); if (mode == FPGAMGRREGS_MODE_INITPHASE || mode == FPGAMGRREGS_MODE_USERMODE) @@ -255,19 +255,19 @@ static int fpgamgr_program_poll_initphase(struct fpgamgr *mgr) } /* Ensure the FPGA entering user mode */ -static int fpgamgr_program_poll_usermode(struct fpgamgr *mgr) +static int socfpga_fpgamgr_program_poll_usermode(struct fpgamgr *mgr) { uint32_t val; uint64_t start; /* additional clocks for the CB to exit initialization phase */ - if (fpgamgr_dclkcnt_set(mgr, 0x5000) != 0) + if (socfpga_fpgamgr_dclkcnt_set(mgr, 0x5000) != 0) return -7; /* (5) wait until FPGA enter user mode */ start = get_time_ns(); while (1) { - if (fpgamgr_get_mode(mgr) == FPGAMGRREGS_MODE_USERMODE) + if (socfpga_fpgamgr_get_mode(mgr) == FPGAMGRREGS_MODE_USERMODE) break; if (is_timeout(start, 100 * MSECOND)) return -ETIMEDOUT; @@ -285,7 +285,7 @@ static int fpgamgr_program_poll_usermode(struct fpgamgr *mgr) * Using FPGA Manager to program the FPGA * Return 0 for sucess */ -static int fpgamgr_program_start(struct firmware_handler *fh) +static int socfpga_fpgamgr_program_start(struct firmware_handler *fh) { struct fpgamgr *mgr = container_of(fh, struct fpgamgr, fh); int status; @@ -295,19 +295,10 @@ static int fpgamgr_program_start(struct firmware_handler *fh) /* disable all signals from hps peripheral controller to fpga */ writel(0, SYSMGR_FPGAINTF_MODULE); - /* disable all signals from fpga to hps sdram */ - writel(0, (CYCLONE5_SDR_ADDRESS + SDR_CTRLGRP_FPGAPORTRST_ADDRESS)); - - /* disable all axi bridge (hps2fpga, lwhps2fpga & fpga2hps) */ - writel(~0, CYCLONE5_RSTMGR_ADDRESS + RESET_MGR_BRG_MOD_RESET_OFS); - - /* unmap the bridges from NIC-301 */ - writel(0x1, CYCLONE5_L3REGS_ADDRESS); - dev_dbg(&mgr->dev, "start programming...\n"); /* initialize the FPGA Manager */ - status = fpgamgr_program_init(mgr); + status = socfpga_fpgamgr_program_init(mgr); if (status) { dev_err(&mgr->dev, "program init failed with: %s\n", strerror(-status)); @@ -318,7 +309,7 @@ static int fpgamgr_program_start(struct firmware_handler *fh) } /* Write the RBF data to FPGA Manager */ -static int fpgamgr_program_write_buf(struct firmware_handler *fh, const void *buf, +static int socfpga_fpgamgr_program_write_buf(struct firmware_handler *fh, const void *buf, size_t size) { struct fpgamgr *mgr = container_of(fh, struct fpgamgr, fh); @@ -349,13 +340,14 @@ static int fpgamgr_program_write_buf(struct firmware_handler *fh, const void *bu return 0; } -static int fpgamgr_program_finish(struct firmware_handler *fh) +static int socfpga_fpgamgr_program_finish(struct firmware_handler *fh) { struct fpgamgr *mgr = container_of(fh, struct fpgamgr, fh); int status; + void (*ocram_func)(void __iomem *ocram_base); /* Ensure the FPGA entering config done */ - status = fpgamgr_program_poll_cd(mgr); + status = socfpga_fpgamgr_program_poll_cd(mgr); if (status) { dev_err(&mgr->dev, "poll for config done failed with: %s\n", strerror(-status)); @@ -365,7 +357,7 @@ static int fpgamgr_program_finish(struct firmware_handler *fh) dev_dbg(&mgr->dev, "waiting for init phase...\n"); /* Ensure the FPGA entering init phase */ - status = fpgamgr_program_poll_initphase(mgr); + status = socfpga_fpgamgr_program_poll_initphase(mgr); if (status) { dev_err(&mgr->dev, "poll for init phase failed with: %s\n", strerror(-status)); @@ -375,13 +367,26 @@ static int fpgamgr_program_finish(struct firmware_handler *fh) dev_dbg(&mgr->dev, "waiting for user mode...\n"); /* Ensure the FPGA entering user mode */ - status = fpgamgr_program_poll_usermode(mgr); + status = socfpga_fpgamgr_program_poll_usermode(mgr); if (status) { dev_err(&mgr->dev, "poll for user mode with: %s\n", strerror(-status)); return status; } + remap_range((void *)CYCLONE5_OCRAM_ADDRESS, PAGE_SIZE, MAP_CACHED); + + dev_dbg(&mgr->dev, "Setting APPLYCFG bit...\n"); + + ocram_func = fncpy((void __iomem *)CYCLONE5_OCRAM_ADDRESS, + &socfpga_sdram_apply_static_cfg, + socfpga_sdram_apply_static_cfg_sz); + + sync_caches_for_execution(); + + ocram_func((void __iomem *) (CYCLONE5_SDR_ADDRESS + + SDR_CTRLGRP_STATICCFG_ADDRESS)); + return 0; } @@ -389,11 +394,11 @@ static int fpgamgr_program_finish(struct firmware_handler *fh) static int programmed_get(struct param_d *p, void *priv) { struct fpgamgr *mgr = priv; - mgr->programmed = fpgamgr_get_mode(mgr) == FPGAMGRREGS_MODE_USERMODE; + mgr->programmed = socfpga_fpgamgr_get_mode(mgr) == FPGAMGRREGS_MODE_USERMODE; return 0; } -static int fpgamgr_probe(struct device_d *dev) +static int socfpga_fpgamgr_probe(struct device_d *dev) { struct resource *iores; struct fpgamgr *mgr; @@ -427,9 +432,9 @@ static int fpgamgr_probe(struct device_d *dev) else fh->id = xstrdup("socfpga-fpga"); - fh->open = fpgamgr_program_start; - fh->write = fpgamgr_program_write_buf; - fh->close = fpgamgr_program_finish; + fh->open = socfpga_fpgamgr_program_start; + fh->write = socfpga_fpgamgr_program_write_buf; + fh->close = socfpga_fpgamgr_program_finish; of_property_read_string(dev->device_node, "compatible", &model); if (model) fh->model = xstrdup(model); @@ -451,6 +456,8 @@ static int fpgamgr_probe(struct device_d *dev) } fh->dev = &mgr->dev; + fh->device_node = dev->device_node; + ret = firmwaremgr_register(fh); if (ret != 0) { free(mgr); @@ -467,16 +474,16 @@ out: return ret; } -static struct of_device_id fpgamgr_id_table[] = { +static struct of_device_id socfpga_fpgamgr_id_table[] = { { .compatible = "altr,socfpga-fpga-mgr", }, { /* sentinel */ } }; -static struct driver_d fpgamgr_driver = { +static struct driver_d socfpga_fpgamgr_driver = { .name = "socfpa-fpgamgr", - .of_compatible = DRV_OF_COMPAT(fpgamgr_id_table), - .probe = fpgamgr_probe, + .of_compatible = DRV_OF_COMPAT(socfpga_fpgamgr_id_table), + .probe = socfpga_fpgamgr_probe, }; -device_platform_driver(fpgamgr_driver); +device_platform_driver(socfpga_fpgamgr_driver); diff --git a/drivers/firmware/socfpga_sdr.S b/drivers/firmware/socfpga_sdr.S new file mode 100644 index 0000000000..b895fb293c --- /dev/null +++ b/drivers/firmware/socfpga_sdr.S @@ -0,0 +1,20 @@ +#include <linux/linkage.h> + + .arch armv7-a + .arm + +/* + * r0 : sdram controller staticcfg + */ + +ENTRY(socfpga_sdram_apply_static_cfg) + push {ip,lr} + ldr r1, [r0] + orr r1, r1, #8 + str r1, [r0] + pop {ip,pc} + .align +ENDPROC(socfpga_sdram_apply_static_cfg) + +ENTRY(socfpga_sdram_apply_static_cfg_sz) + .word . - socfpga_sdram_apply_static_cfg; diff --git a/drivers/firmware/zynqmp-fpga.c b/drivers/firmware/zynqmp-fpga.c index ab70d99933..0fc229bfd3 100644 --- a/drivers/firmware/zynqmp-fpga.c +++ b/drivers/firmware/zynqmp-fpga.c @@ -383,6 +383,8 @@ static int zynqmp_fpga_probe(struct device_d *dev) } fh->dev = &mgr->dev; + fh->device_node = dev->device_node; + ret = firmwaremgr_register(fh); if (ret != 0) { free(mgr); diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig new file mode 100644 index 0000000000..64ce9f91b6 --- /dev/null +++ b/drivers/fpga/Kconfig @@ -0,0 +1,30 @@ +# +# FPGA framework configuration +# + +menu "FPGA Configuration Support" + +config FPGA + tristate "FPGA Configuration Framework" + help + Say Y here if you want support for configuring FPGAs from barebox. + +if FPGA + +config FPGA_BRIDGE + tristate "FPGA Bridge Framework" + help + Say Y here if you want to support bridges connected between host + processors and FPGAs or between FPGAs. + +config SOCFPGA_FPGA_BRIDGE + tristate "Altera SoCFPGA FPGA Bridges" + depends on ARCH_SOCFPGA && FPGA_BRIDGE + select RESET_CONTROLLER + help + Say Y to enable drivers for FPGA bridges for Altera SOCFPGA + devices. + +endif # FPGA + +endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile new file mode 100644 index 0000000000..86178fe7c0 --- /dev/null +++ b/drivers/fpga/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the fpga framework and fpga manager drivers. +# + +# FPGA Bridge Drivers +obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o +obj-$(CONFIG_SOCFPGA_FPGA_BRIDGE) += socfpga-hps2fpga-bridge.o socfpga-fpga2sdram-bridge.o diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c new file mode 100644 index 0000000000..6f9e943de9 --- /dev/null +++ b/drivers/fpga/fpga-bridge.c @@ -0,0 +1,243 @@ +/* + * FPGA Bridge Framework Driver + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <common.h> +#include <fpga-bridge.h> + +/** + * fpga_bridge_enable - Enable transactions on the bridge + * + * @bridge: FPGA bridge + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_enable(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "enable\n"); + + if (bridge->br_ops && bridge->br_ops->enable_set) + return bridge->br_ops->enable_set(bridge, 1); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_enable); + +/** + * fpga_bridge_disable - Disable transactions on the bridge + * + * @bridge: FPGA bridge + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_disable(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "disable\n"); + + if (bridge->br_ops && bridge->br_ops->enable_set) + return bridge->br_ops->enable_set(bridge, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_disable); + +/** + * of_fpga_bridge_get - get an exclusive reference to a fpga bridge + * + * @np: node pointer of a FPGA bridge + * + * Return fpga_bridge struct if successful. + * Return -EBUSY if someone already has a reference to the bridge. + * Return -ENODEV if @np is not a FPGA Bridge. + */ +struct fpga_bridge *of_fpga_bridge_get(struct device_node *np) + +{ + struct device_d *dev; + struct fpga_bridge *bridge; + int ret = -EPROBE_DEFER; + + dev = of_find_device_by_node(np); + if (!dev || !dev->priv) + return ERR_PTR(ret); + + bridge = dev->priv; + + return bridge; +} +EXPORT_SYMBOL_GPL(of_fpga_bridge_get); + +/** + * fpga_bridges_enable - enable bridges in a list + * @bridge_list: list of FPGA bridges + * + * Enable each bridge in the list. If list is empty, do nothing. + * + * Return 0 for success or empty bridge list; return error code otherwise. + */ +int fpga_bridges_enable(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + int ret; + + list_for_each_entry(bridge, bridge_list, node) { + ret = fpga_bridge_enable(bridge); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridges_enable); + +/** + * fpga_bridges_disable - disable bridges in a list + * + * @bridge_list: list of FPGA bridges + * + * Disable each bridge in the list. If list is empty, do nothing. + * + * Return 0 for success or empty bridge list; return error code otherwise. + */ +int fpga_bridges_disable(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + int ret; + + list_for_each_entry(bridge, bridge_list, node) { + ret = fpga_bridge_disable(bridge); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridges_disable); + +/** + * fpga_bridges_put - put bridges + * + * @bridge_list: list of FPGA bridges + * + * For each bridge in the list, put the bridge and remove it from the list. + * If list is empty, do nothing. + */ +void fpga_bridges_put(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge, *next; + + list_for_each_entry_safe(bridge, next, bridge_list, node) + list_del(&bridge->node); +} +EXPORT_SYMBOL_GPL(fpga_bridges_put); + +/** + * fpga_bridges_get_to_list - get a bridge, add it to a list + * + * @np: node pointer of a FPGA bridge + * @bridge_list: list of FPGA bridges + * + * Get an exclusive reference to the bridge and and it to the list. + * + * Return 0 for success, error code from of_fpga_bridge_get() othewise. + */ +int fpga_bridge_get_to_list(struct device_node *np, + struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + + bridge = of_fpga_bridge_get(np); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + list_add(&bridge->node, bridge_list); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_get_to_list); + +static int set_enable(struct param_d *p, void *priv) +{ + struct fpga_bridge *bridge = priv; + + if (bridge->enable) + fpga_bridge_enable(bridge); + else + fpga_bridge_disable(bridge); + + return 0; +} + +/** + * fpga_bridge_register - register a fpga bridge driver + * @dev: FPGA bridge device from pdev + * @name: FPGA bridge name + * @br_ops: pointer to structure of fpga bridge ops + * @priv: FPGA bridge private data + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_register(struct device_d *dev, const char *name, + const struct fpga_bridge_ops *br_ops, void *priv) +{ + struct fpga_bridge *bridge; + struct param_d *p; + int ret = 0; + + if (!name || !strlen(name)) { + dev_err(dev, "Attempt to register with no name!\n"); + return -EINVAL; + } + + bridge = xzalloc(sizeof(*bridge)); + if (!bridge) + return -ENOMEM; + + INIT_LIST_HEAD(&bridge->node); + + bridge->br_ops = br_ops; + bridge->priv = priv; + + bridge->dev.parent = dev; + bridge->dev.device_node = dev->device_node; + bridge->dev.id = DEVICE_ID_DYNAMIC; + + bridge->dev.name = xstrdup(name); + + ret = register_device(&bridge->dev); + if (ret) + goto out; + + dev->priv = bridge; + + bridge->enable = 0; + p = dev_add_param_bool(&bridge->dev, "enable", set_enable, + NULL, &bridge->enable, bridge); + if (IS_ERR(p)) + return PTR_ERR(p); + + of_platform_populate(dev->device_node, NULL, dev); + + dev_info(bridge->dev.parent, "fpga bridge [%s] registered\n", + bridge->dev.name); + + return 0; + +out: + kfree(bridge); + + return ret; +} +EXPORT_SYMBOL_GPL(fpga_bridge_register); diff --git a/drivers/fpga/socfpga-fpga2sdram-bridge.c b/drivers/fpga/socfpga-fpga2sdram-bridge.c new file mode 100644 index 0000000000..a9760597dd --- /dev/null +++ b/drivers/fpga/socfpga-fpga2sdram-bridge.c @@ -0,0 +1,139 @@ +/* + * FPGA to SDRAM Bridge Driver for Altera SoCFPGA Devices + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This driver manages a bridge between an FPGA and the SDRAM used by the ARM + * host processor system (HPS). + * + * The bridge contains 4 read ports, 4 write ports, and 6 command ports. + * Reconfiguring these ports requires that no SDRAM transactions occur during + * reconfiguration. The code reconfiguring the ports cannot run out of SDRAM + * nor can the FPGA access the SDRAM during reconfiguration. This driver does + * not support reconfiguring the ports. The ports are configured by code + * running out of on chip ram before Linux is started and the configuration + * is passed in a handoff register in the system manager. + * + * This driver supports enabling and disabling of the configured ports, which + * allows for safe reprogramming of the FPGA, assuming that the new FPGA image + * uses the same port configuration. Bridges must be disabled before + * reprogramming the FPGA and re-enabled after the FPGA has been programmed. + */ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <fpga-bridge.h> +#include <mfd/syscon.h> +#include <of_device.h> +#include <linux/clk.h> +#include <linux/reset.h> + +#define SOCFPGA_SDRCTL_ADDR 0xffc25000 +#define ALT_SDR_CTL_FPGAPORTRST_OFST 0x80 +#define ALT_SDR_CTL_FPGAPORTRST_PORTRSTN_MSK 0x00003fff +#define ALT_SDR_CTL_FPGAPORTRST_RD_SHIFT 0 +#define ALT_SDR_CTL_FPGAPORTRST_WR_SHIFT 4 +#define ALT_SDR_CTL_FPGAPORTRST_CTRL_SHIFT 8 + +#define SOCFPGA_SYSMGR_ADDR 0xffd08000 +/* + * From the Cyclone V HPS Memory Map document: + * These registers are used to store handoff information between the + * preloader and the OS. These 8 registers can be used to store any + * information. The contents of these registers have no impact on + * the state of the HPS hardware. + */ +#define SYSMGR_ISWGRP_HANDOFF3 (0x8C) + +#define F2S_BRIDGE_NAME "fpga2sdram" + +struct alt_fpga2sdram_data { + struct device_d *dev; + int mask; +}; + +static inline int _alt_fpga2sdram_enable_set(struct alt_fpga2sdram_data *priv, + bool enable) +{ + int val; + + val = readl(SOCFPGA_SDRCTL_ADDR + ALT_SDR_CTL_FPGAPORTRST_OFST); + + if (enable) + val |= priv->mask; + else + val = 0; + + /* The kernel driver expects this value in this register :-( */ + writel(priv->mask, SOCFPGA_SYSMGR_ADDR + SYSMGR_ISWGRP_HANDOFF3); + + dev_dbg(priv->dev, "setting fpgaportrst to 0x%08x\n", val); + + return writel(val, SOCFPGA_SDRCTL_ADDR + ALT_SDR_CTL_FPGAPORTRST_OFST); +} + +static int alt_fpga2sdram_enable_set(struct fpga_bridge *bridge, bool enable) +{ + return _alt_fpga2sdram_enable_set(bridge->priv, enable); +} + +struct prop_map { + char *prop_name; + u32 *prop_value; + u32 prop_max; +}; + +static const struct fpga_bridge_ops altera_fpga2sdram_br_ops = { + .enable_set = alt_fpga2sdram_enable_set, +}; + +static struct of_device_id altera_fpga_of_match[] = { + { .compatible = "altr,socfpga-fpga2sdram-bridge" }, + {}, +}; + +static int alt_fpga_bridge_probe(struct device_d *dev) +{ + struct alt_fpga2sdram_data *priv; + int ret = 0; + + priv = xzalloc(sizeof(*priv)); + if (!priv) + return -ENOMEM; + + /* enable all ports for now */ + priv->mask = ALT_SDR_CTL_FPGAPORTRST_PORTRSTN_MSK; + + priv->dev = dev; + + ret = fpga_bridge_register(dev, F2S_BRIDGE_NAME, + &altera_fpga2sdram_br_ops, priv); + if (ret) + return ret; + + dev_info(dev, "driver initialized with handoff %08x\n", priv->mask); + + return ret; +} + +static struct driver_d altera_fpga_driver = { + .probe = alt_fpga_bridge_probe, + .name = "altera-fpga2sdram-bridge", + .of_compatible = DRV_OF_COMPAT(altera_fpga_of_match), +}; +device_platform_driver(altera_fpga_driver); diff --git a/drivers/fpga/socfpga-hps2fpga-bridge.c b/drivers/fpga/socfpga-hps2fpga-bridge.c new file mode 100644 index 0000000000..ecb33ea0b3 --- /dev/null +++ b/drivers/fpga/socfpga-hps2fpga-bridge.c @@ -0,0 +1,179 @@ +/* + * FPGA to/from HPS Bridge Driver for Altera SoCFPGA Devices + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * Includes this patch from the mailing list: + * fpga: altera-hps2fpga: fix HPS2FPGA bridge visibility to L3 masters + * Signed-off-by: Anatolij Gustschin <agust@denx.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This driver manages bridges on a Altera SOCFPGA between the ARM host + * processor system (HPS) and the embedded FPGA. + * + * This driver supports enabling and disabling of the configured ports, which + * allows for safe reprogramming of the FPGA, assuming that the new FPGA image + * uses the same port configuration. Bridges must be disabled before + * reprogramming the FPGA and re-enabled after the FPGA has been programmed. + */ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <fpga-bridge.h> +#include <mfd/syscon.h> +#include <of_device.h> +#include <linux/clk.h> +#include <linux/reset.h> + +#define SOCFPGA_L3_ADDR 0xff800000 +#define ALT_L3_REMAP_OFST 0x0 +#define ALT_L3_REMAP_MPUZERO_MSK 0x00000001 +#define ALT_L3_REMAP_H2F_MSK 0x00000008 +#define ALT_L3_REMAP_LWH2F_MSK 0x00000010 + +#define HPS2FPGA_BRIDGE_NAME "hps2fpga" +#define LWHPS2FPGA_BRIDGE_NAME "lwhps2fpga" +#define FPGA2HPS_BRIDGE_NAME "fpga2hps" + +struct altera_hps2fpga_data { + struct device_d *dev; + const char *name; + struct reset_control *bridge_reset; + unsigned int remap_mask; + struct clk *clk; +}; + +/* The L3 REMAP register is write only, so keep a cached value. */ +static unsigned int l3_remap_shadow; + +static int _alt_hps2fpga_enable_set(struct altera_hps2fpga_data *priv, + bool enable) +{ + int ret; + + /* bring bridge out of reset */ + if (enable) + ret = reset_control_deassert(priv->bridge_reset); + else + ret = reset_control_assert(priv->bridge_reset); + if (ret) + return ret; + + /* Allow bridge to be visible to L3 masters or not */ + if (priv->remap_mask) { + l3_remap_shadow |= ALT_L3_REMAP_MPUZERO_MSK; + + if (enable) + l3_remap_shadow |= priv->remap_mask; + else + l3_remap_shadow &= ~priv->remap_mask; + + dev_dbg(priv->dev, "setting L3 visibility to 0x%08x\n", + l3_remap_shadow); + + writel(l3_remap_shadow, SOCFPGA_L3_ADDR + ALT_L3_REMAP_OFST); + } + + return ret; +} + +static int alt_hps2fpga_enable_set(struct fpga_bridge *bridge, bool enable) +{ + return _alt_hps2fpga_enable_set(bridge->priv, enable); +} + +static const struct fpga_bridge_ops altera_hps2fpga_br_ops = { + .enable_set = alt_hps2fpga_enable_set, +}; + +static struct altera_hps2fpga_data hps2fpga_data = { + .name = HPS2FPGA_BRIDGE_NAME, + .remap_mask = ALT_L3_REMAP_H2F_MSK, +}; + +static struct altera_hps2fpga_data lwhps2fpga_data = { + .name = LWHPS2FPGA_BRIDGE_NAME, + .remap_mask = ALT_L3_REMAP_LWH2F_MSK, +}; + +static struct altera_hps2fpga_data fpga2hps_data = { + .name = FPGA2HPS_BRIDGE_NAME, +}; + +static struct of_device_id altera_fpga_of_match[] = { + { .compatible = "altr,socfpga-hps2fpga-bridge", + .data = &hps2fpga_data }, + { .compatible = "altr,socfpga-lwhps2fpga-bridge", + .data = &lwhps2fpga_data }, + { .compatible = "altr,socfpga-fpga2hps-bridge", + .data = &fpga2hps_data }, + { /* sentinel */ }, +}; + +static int alt_fpga_bridge_probe(struct device_d *dev) +{ + struct altera_hps2fpga_data *priv; + const struct of_device_id *of_id; + u32 enable; + int ret; + + of_id = of_match_device(altera_fpga_of_match, dev); + priv = (struct altera_hps2fpga_data *)of_id->data; + + priv->bridge_reset = of_reset_control_get(dev->device_node, NULL); + if (IS_ERR(priv->bridge_reset)) { + dev_err(dev, "Could not get %s reset control\n", priv->name); + return PTR_ERR(priv->bridge_reset); + } + + priv->clk = clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "no clock specified\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_enable(priv->clk); + if (ret) { + dev_err(dev, "could not enable clock\n"); + return -EBUSY; + } + + priv->dev = dev; + + if (!of_property_read_u32(dev->device_node, "bridge-enable", &enable)) { + if (enable > 1) { + dev_warn(dev, "invalid bridge-enable %u > 1\n", enable); + } else { + dev_info(dev, "%s bridge\n", + (enable ? "enabling" : "disabling")); + + ret = _alt_hps2fpga_enable_set(priv, enable); + if (ret) + return ret; + } + } + + return fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops, + priv); +} + +static struct driver_d alt_fpga_bridge_driver = { + .probe = alt_fpga_bridge_probe, + .name = "altera-hps2fpga-bridge", + .of_compatible = DRV_OF_COMPAT(altera_fpga_of_match), +}; +device_platform_driver(alt_fpga_bridge_driver); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index a8ee9e58b8..98a44fbbb5 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -171,6 +171,14 @@ config GPIO_SIFIVE help Say yes here to support the GPIO device on SiFive SoCs. +config GPIO_STARFIVE + bool "StarFive GPIO support" + depends on SOC_STARFIVE || CROSS_COMPILE + depends on OF_GPIO + select GPIO_GENERIC + help + Say yes here to support the GPIO device on StarFive SoCs. + config GPIO_LIBFTDI1 bool "libftdi1 driver" depends on SANDBOX diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 25e12105d8..638cbb19a3 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -24,3 +24,4 @@ obj-$(CONFIG_GPIO_SX150X) += gpio-sx150x.o obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o +obj-$(CONFIG_GPIO_STARFIVE) += gpio-starfive-vic.o diff --git a/drivers/gpio/gpio-starfive-vic.c b/drivers/gpio/gpio-starfive-vic.c new file mode 100644 index 0000000000..baa4f584d5 --- /dev/null +++ b/drivers/gpio/gpio-starfive-vic.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT 2020 Shanghai StarFive Technology Co., Ltd. + */ + +#include <linux/basic_mmio_gpio.h> +#include <linux/reset.h> +#include <linux/clk.h> +#include <printk.h> +#include <driver.h> +#include <errno.h> +#include <pinctrl.h> + +#define GPIO_EN 0x0 +#define GPIO_IS_LOW 0x10 +#define GPIO_IS_HIGH 0x14 +#define GPIO_IBE_LOW 0x18 +#define GPIO_IBE_HIGH 0x1c +#define GPIO_IEV_LOW 0x20 +#define GPIO_IEV_HIGH 0x24 +#define GPIO_IE_LOW 0x28 +#define GPIO_IE_HIGH 0x2c +#define GPIO_IC_LOW 0x30 +#define GPIO_IC_HIGH 0x34 +//read only +#define GPIO_RIS_LOW 0x38 +#define GPIO_RIS_HIGH 0x3c +#define GPIO_MIS_LOW 0x40 +#define GPIO_MIS_HIGH 0x44 +#define GPIO_DIN_LOW 0x48 +#define GPIO_DIN_HIGH 0x4c + +#define GPIO_DOUT_X_REG 0x50 +#define GPIO_DOEN_X_REG 0x54 + +#define MAX_GPIO 64 + +struct starfive_gpio { + void __iomem *base; + struct gpio_chip gc; +}; + +#define to_starfive_gpio(gc) container_of(gc, struct starfive_gpio, gc) + +static int starfive_direction_input(struct gpio_chip *gc, unsigned offset) +{ + struct starfive_gpio *chip = to_starfive_gpio(gc); + + if (offset >= gc->ngpio) + return -EINVAL; + + writel(0x1, chip->base + GPIO_DOEN_X_REG + offset * 8); + + return 0; +} + +static int starfive_direction_output(struct gpio_chip *gc, unsigned offset, int value) +{ + struct starfive_gpio *chip = to_starfive_gpio(gc); + + if (offset >= gc->ngpio) + return -EINVAL; + writel(0x0, chip->base + GPIO_DOEN_X_REG + offset * 8); + writel(value, chip->base + GPIO_DOUT_X_REG + offset * 8); + + return 0; +} + +static int starfive_get_direction(struct gpio_chip *gc, unsigned offset) +{ + struct starfive_gpio *chip = to_starfive_gpio(gc); + + if (offset >= gc->ngpio) + return -EINVAL; + + return readl(chip->base + GPIO_DOEN_X_REG + offset * 8) & 0x1; +} + +static int starfive_get_value(struct gpio_chip *gc, unsigned offset) +{ + struct starfive_gpio *chip = to_starfive_gpio(gc); + int value; + + if (offset >= gc->ngpio) + return -EINVAL; + + if(offset < 32){ + value = readl(chip->base + GPIO_DIN_LOW); + return (value >> offset) & 0x1; + } else { + value = readl(chip->base + GPIO_DIN_HIGH); + return (value >> (offset - 32)) & 0x1; + } +} + +static void starfive_set_value(struct gpio_chip *gc, unsigned offset, int value) +{ + struct starfive_gpio *chip = to_starfive_gpio(gc); + + if (offset >= gc->ngpio) + return; + + writel(value, chip->base + GPIO_DOUT_X_REG + offset * 8); +} + +static struct gpio_ops starfive_gpio_ops = { + .direction_input = starfive_direction_input, + .direction_output = starfive_direction_output, + .get_direction = starfive_get_direction, + .get = starfive_get_value, + .set = starfive_set_value, +}; + +static int starfive_gpio_probe(struct device_d *dev) +{ + struct starfive_gpio *chip; + struct resource *res; + struct clk *clk; + int ret; + + clk = clk_get(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + clk_enable(clk); + + ret = device_reset(dev); + if (ret) + return ret; + + ret = pinctrl_single_probe(dev); + if (ret) + return ret; + + res = dev_get_resource(dev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) + return PTR_ERR(res); + + chip = xzalloc(sizeof(*chip)); + chip->base = IOMEM(res->start); + + chip->gc.base = -1; + chip->gc.ngpio = MAX_GPIO; + chip->gc.dev = dev; + chip->gc.ops = &starfive_gpio_ops; + + /* Disable all GPIO interrupts */ + iowrite32(0, chip->base + GPIO_IE_HIGH); + iowrite32(0, chip->base + GPIO_IE_LOW); + + ret = gpiochip_add(&chip->gc); + if (ret) { + dev_err(dev, "could not add gpiochip\n"); + gpiochip_remove(&chip->gc); + return ret; + } + + writel(1, chip->base + GPIO_EN); + + return 0; +} + +static const struct of_device_id starfive_gpio_match[] = { + { .compatible = "starfive,gpio0", }, + { }, +}; + +static struct driver_d starfive_gpio_driver = { + .probe = starfive_gpio_probe, + .name = "starfive_gpio", + .of_compatible = starfive_gpio_match, +}; +postcore_platform_driver(starfive_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Huan Feng <huan.feng@starfivetech.com>"); +MODULE_DESCRIPTION("Starfive VIC GPIO generator driver"); diff --git a/drivers/hab/habv4.c b/drivers/hab/habv4.c index c2acb81369..d58768fa54 100644 --- a/drivers/hab/habv4.c +++ b/drivers/hab/habv4.c @@ -553,8 +553,7 @@ static int habv4_get_status(const struct habv4_rvt *rvt) break; /* suppress RNG self-test fail events if they can be handled in software */ - if (IS_ENABLED(CONFIG_CRYPTO_DEV_FSL_CAAM_RNG_SELF_TEST) && - is_known_rng_fail_event(data, len)) { + if (is_known_rng_fail_event(data, len)) { pr_debug("RNG self-test failure detected, will run software self-test\n"); } else { pr_err("-------- HAB Event %d --------\n", i); diff --git a/drivers/hw_random/Kconfig b/drivers/hw_random/Kconfig index a84c03efef..764911f4d3 100644 --- a/drivers/hw_random/Kconfig +++ b/drivers/hw_random/Kconfig @@ -36,4 +36,11 @@ config HW_RANDOM_VIRTIO This driver provides guest-side support for the virtual Random Number Generator hardware. +config HW_RANDOM_STARFIVE + tristate "StarFive Random Number Generator" + depends on SOC_STARFIVE || COMPILE_TEST + help + This driver provides barebox support for the Random Number + Generator hardware found on the StarFive family of SoCs. + endif diff --git a/drivers/hw_random/Makefile b/drivers/hw_random/Makefile index 4bab3967fc..4cf33d2d93 100644 --- a/drivers/hw_random/Makefile +++ b/drivers/hw_random/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_HWRNG_MXC_RNGC) += mxc-rngc.o obj-$(CONFIG_HWRNG_STM32) += stm32-rng.o obj-$(CONFIG_HWRNG_DEV_RANDOM) += dev-random.o obj-$(CONFIG_HW_RANDOM_VIRTIO) += virtio-rng.o +obj-$(CONFIG_HW_RANDOM_STARFIVE) += starfive-vic-rng.o diff --git a/drivers/hw_random/starfive-vic-rng.c b/drivers/hw_random/starfive-vic-rng.c new file mode 100644 index 0000000000..f7b7585884 --- /dev/null +++ b/drivers/hw_random/starfive-vic-rng.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT 2020 Shanghai StarFive Technology Co., Ltd. + */ +#include <common.h> +#include <linux/err.h> +#include <io.h> +#include <of.h> +#include <driver.h> +#include <linux/hw_random.h> +#include <linux/reset.h> +#include <linux/clk.h> + +#define VIC_RAND_LEN 4 + +#define VIC_CTRL 0x00 +#define VIC_MODE 0x04 +#define VIC_SMODE 0x08 +#define VIC_STAT 0x0C +#define VIC_IE 0x10 +#define VIC_ISTAT 0x14 +#define VIC_ALARM 0x18 +#define VIC_BUILD_ID 0x1C +#define VIC_FEATURES 0x20 +#define VIC_RAND0 0x24 +#define VIC_NPA_DATA0 0x34 +#define VIC_SEED0 0x74 +#define VIC_IA_RDATA 0xA4 +#define VIC_IA_WDATA 0xA8 +#define VIC_IA_ADDR 0xAC +#define VIC_IA_CMD 0xB0 + +/* CTRL */ +#define VIC_CTRL_CMD_NOP 0 +#define VIC_CTRL_CMD_GEN_NOISE 1 +#define VIC_CTRL_CMD_GEN_NONCE 2 +#define VIC_CTRL_CMD_CREATE_STATE 3 +#define VIC_CTRL_CMD_RENEW_STATE 4 +#define VIC_CTRL_CMD_REFRESH_ADDIN 5 +#define VIC_CTRL_CMD_GEN_RANDOM 6 +#define VIC_CTRL_CMD_ADVANCE_STATE 7 +#define VIC_CTRL_CMD_KAT 8 +#define VIC_CTRL_CMD_ZEROIZE 15 + +/* SMODE */ +#define _VIC_SMODE_SECURE_EN 1 + +#define VIC_SMODE_SECURE_EN(x) ((x) << _VIC_SMODE_SECURE_EN) + +/* STAT */ +#define _VIC_STAT_BUSY 31 + +#define VIC_STAT_BUSY (1UL << _VIC_STAT_BUSY) + +/* IE */ +#define _VIC_IE_GLBL 31 +#define _VIC_IE_DONE 4 +#define _VIC_IE_ALARMS 3 +#define _VIC_IE_NOISE_RDY 2 +#define _VIC_IE_KAT_COMPLETE 1 +#define _VIC_IE_ZEROIZE 0 + +#define VIC_IE_GLBL (1UL << _VIC_IE_GLBL) +#define VIC_IE_DONE (1UL << _VIC_IE_DONE) +#define VIC_IE_ALARMS (1UL << _VIC_IE_ALARMS) +#define VIC_IE_NOISE_RDY (1UL << _VIC_IE_NOISE_RDY) +#define VIC_IE_KAT_COMPLETE (1UL << _VIC_IE_KAT_COMPLETE) +#define VIC_IE_ZEROIZE (1UL << _VIC_IE_ZEROIZE) +#define VIC_IE_ALL (VIC_IE_GLBL | VIC_IE_DONE | VIC_IE_ALARMS | \ + VIC_IE_NOISE_RDY | VIC_IE_KAT_COMPLETE | VIC_IE_ZEROIZE) + +#define to_vic_rng(p) container_of(p, struct vic_rng, rng) + +struct vic_rng { + struct device_d *dev; + void __iomem *base; + struct hwrng rng; +}; + +static inline void vic_wait_till_idle(struct vic_rng *hrng) +{ + while(readl(hrng->base + VIC_STAT) & VIC_STAT_BUSY) + ; +} + +static inline void vic_rng_irq_mask_clear(struct vic_rng *hrng) +{ + u32 data = readl(hrng->base + VIC_ISTAT); + writel(data, hrng->base + VIC_ISTAT); + writel(0, hrng->base + VIC_ALARM); +} + +static int vic_trng_cmd(struct vic_rng *hrng, u32 cmd) +{ + vic_wait_till_idle(hrng); + + switch (cmd) { + case VIC_CTRL_CMD_NOP: + case VIC_CTRL_CMD_GEN_NOISE: + case VIC_CTRL_CMD_GEN_NONCE: + case VIC_CTRL_CMD_CREATE_STATE: + case VIC_CTRL_CMD_RENEW_STATE: + case VIC_CTRL_CMD_REFRESH_ADDIN: + case VIC_CTRL_CMD_GEN_RANDOM: + case VIC_CTRL_CMD_ADVANCE_STATE: + case VIC_CTRL_CMD_KAT: + case VIC_CTRL_CMD_ZEROIZE: + writel(cmd, hrng->base + VIC_CTRL); + return 0; + default: + return -EINVAL; + } +} + +static int vic_rng_init(struct hwrng *rng) +{ + struct vic_rng *hrng = to_vic_rng(rng); + struct clk *clk; + int ret; + + clk = clk_get(rng->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + clk_enable(clk); + + ret = device_reset(rng->dev); + if (ret) + return ret; + + // clear register: ISTAT + vic_rng_irq_mask_clear(hrng); + + // set mission mode + writel(VIC_SMODE_SECURE_EN(1), hrng->base + VIC_SMODE); + + vic_trng_cmd(hrng, VIC_CTRL_CMD_GEN_NOISE); + vic_wait_till_idle(hrng); + + // set interrupt + writel(VIC_IE_ALL, hrng->base + VIC_IE); + + // zeroize + vic_trng_cmd(hrng, VIC_CTRL_CMD_ZEROIZE); + + vic_wait_till_idle(hrng); + + return 0; +} + +static int vic_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + struct vic_rng *hrng = to_vic_rng(rng); + + vic_trng_cmd(hrng, VIC_CTRL_CMD_ZEROIZE); + vic_trng_cmd(hrng, VIC_CTRL_CMD_GEN_NOISE); + vic_trng_cmd(hrng, VIC_CTRL_CMD_CREATE_STATE); + + vic_wait_till_idle(hrng); + max = min_t(size_t, max, VIC_RAND_LEN * 4); + + writel(0x0, hrng->base + VIC_MODE); + vic_trng_cmd(hrng, VIC_CTRL_CMD_GEN_RANDOM); + + vic_wait_till_idle(hrng); + memcpy_fromio(buf, hrng->base + VIC_RAND0, max); + vic_trng_cmd(hrng, VIC_CTRL_CMD_ZEROIZE); + + vic_wait_till_idle(hrng); + return max; +} + +static int vic_rng_probe(struct device_d *dev) +{ + struct vic_rng *hrng; + struct resource *res; + + hrng = xzalloc(sizeof(*hrng)); + + res = dev_request_mem_resource(dev, 0); + if (IS_ERR(res)) + return PTR_ERR(res); + + hrng->base = IOMEM(res->start); + hrng->dev = dev; + + hrng->rng.name = dev_name(dev); + hrng->rng.init = vic_rng_init; + hrng->rng.read = vic_rng_read; + + return hwrng_register(dev, &hrng->rng); +} + +static const struct of_device_id vic_rng_dt_ids[] = { + { .compatible = "starfive,vic-rng" }, + { /* sentinel */ } +}; + +static struct driver_d vic_rng_driver = { + .name = "vic-rng", + .probe = vic_rng_probe, + .of_compatible = vic_rng_dt_ids, +}; +device_platform_driver(vic_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Huan Feng <huan.feng@starfivetech.com>"); +MODULE_DESCRIPTION("Starfive VIC random number generator driver"); diff --git a/drivers/i2c/i2c.c b/drivers/i2c/i2c.c index 57d8c7017f..12aac42794 100644 --- a/drivers/i2c/i2c.c +++ b/drivers/i2c/i2c.c @@ -407,6 +407,9 @@ static struct i2c_client *i2c_new_device(struct i2c_adapter *adapter, } client->dev.info = i2c_info; + if (chip->of_node) + chip->of_node->dev = &client->dev; + return client; } @@ -548,6 +551,11 @@ struct i2c_adapter *i2c_get_adapter(int busnum) struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node) { struct i2c_adapter *adap; + int ret; + + ret = of_device_ensure_probed(node); + if (ret) + return ERR_PTR(ret); for_each_i2c_adapter(adap) if (adap->dev.device_node == node) diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index ff3e9d33f6..ba9fa25a3d 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -73,7 +73,8 @@ config INPUT_SPECIALKEYS config VIRTIO_INPUT bool "Virtio input driver" - depends on VIRTIO && BTHREAD + depends on VIRTIO + select POLLER select INPUT help This driver supports virtio keyboard input devices. diff --git a/drivers/input/virtio_input.c b/drivers/input/virtio_input.c index 9c2e4d923f..b354933209 100644 --- a/drivers/input/virtio_input.c +++ b/drivers/input/virtio_input.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include <common.h> -#include <bthread.h> +#include <poller.h> #include <linux/virtio.h> #include <linux/virtio_config.h> #include <linux/virtio_ring.h> @@ -17,7 +17,7 @@ struct virtio_input { struct virtio_device *vdev; struct virtqueue *evt, *sts; struct virtio_input_event evts[64]; - struct bthread *bthread; + struct poller_struct poller; struct sound_card beeper; unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; }; @@ -100,21 +100,17 @@ static int virtinput_recv_status(struct virtio_input *vi) return i; } -static int virtinput_poll_vqs(void *_vi) +static void virtinput_poll_vqs(struct poller_struct *poller) { - struct virtio_input *vi = _vi; + struct virtio_input *vi = container_of(poller, struct virtio_input, poller); - while (!bthread_should_stop()) { - int bufs = 0; + int bufs = 0; - bufs += virtinput_recv_events(vi); - bufs += virtinput_recv_status(vi); + bufs += virtinput_recv_events(vi); + bufs += virtinput_recv_status(vi); - if (bufs) - virtqueue_kick(vi->evt); - } - - return 0; + if (bufs) + virtqueue_kick(vi->evt); } static u8 virtinput_cfg_select(struct virtio_input *vi, @@ -213,6 +209,8 @@ static int virtinput_probe(struct virtio_device *vdev) name, min(size, sizeof(name))); name[size] = '\0'; + dev_info(&vdev->dev, "'%s' detected\n", name); + virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_SND, vi->sndbit, SND_CNT); @@ -224,12 +222,12 @@ static int virtinput_probe(struct virtio_device *vdev) virtinput_fill_evt(vi); - vi->bthread = bthread_run(virtinput_poll_vqs, vi, - "%s/input0", dev_name(&vdev->dev)); - if (!vi->bthread) { - err = -ENOMEM; - goto err_bthread_run; - } + vi->poller.func = virtinput_poll_vqs; + snprintf(name, sizeof(name), "%s/input0", dev_name(&vdev->dev)); + + err = poller_register(&vi->poller, name); + if (err) + goto err_poller_register; if (IS_ENABLED(CONFIG_SOUND) && (vi->sndbit[0] & (BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE)))) { @@ -246,12 +244,10 @@ static int virtinput_probe(struct virtio_device *vdev) dev_info(&vdev->dev, "bell registered\n"); } - dev_info(&vdev->dev, "'%s' probed\n", name); - return 0; -err_bthread_run: - bthread_free(vi->bthread); +err_poller_register: + input_device_unregister(&vi->idev); err_input_register: vdev->config->del_vqs(vdev); err_init_vq: @@ -263,8 +259,7 @@ static void virtinput_remove(struct virtio_device *vdev) { struct virtio_input *vi = vdev->priv; - bthread_stop(vi->bthread); - bthread_free(vi->bthread); + poller_unregister(&vi->poller); vdev->config->reset(vdev); vdev->config->del_vqs(vdev); diff --git a/drivers/mci/dw_mmc.c b/drivers/mci/dw_mmc.c index 930b538adc..b402090ab3 100644 --- a/drivers/mci/dw_mmc.c +++ b/drivers/mci/dw_mmc.c @@ -17,6 +17,7 @@ #include <io.h> #include <platform_data/dw_mmc.h> #include <linux/bitops.h> +#include <linux/reset.h> #include <linux/clk.h> #include <linux/err.h> #include <errno.h> @@ -31,7 +32,7 @@ struct dwmci_host { unsigned int fifo_size_bytes; struct dwmci_idmac *idmac; - unsigned long clkrate; + u32 clkrate; int ciu_div; u32 fifoth_val; u32 pwren_value; @@ -548,6 +549,7 @@ static int dwmci_init(struct mci_host *mci, struct device_d *dev) static int dw_mmc_probe(struct device_d *dev) { + struct reset_control *rst; struct resource *iores; struct dwmci_host *host; struct dw_mmc_platform_data *pdata = dev->platform_data; @@ -568,6 +570,15 @@ static int dw_mmc_probe(struct device_d *dev) clk_enable(host->clk_biu); clk_enable(host->clk_ciu); + rst = reset_control_get(dev, "reset"); + if (IS_ERR(rst)) { + return PTR_ERR(rst); + } else if (rst) { + reset_control_assert(rst); + udelay(10); + reset_control_deassert(rst); + } + iores = dev_request_mem_resource(dev, 0); if (IS_ERR(iores)) return PTR_ERR(iores); @@ -606,7 +617,11 @@ static int dw_mmc_probe(struct device_d *dev) else host->pwren_value = 1; - host->clkrate = clk_get_rate(host->clk_ciu); + if (of_device_is_compatible(dev->device_node, "starfive,jh7100-dw-mshc")) + of_property_read_u32(dev->device_node, "clock-frequency", &host->clkrate); + if (!host->clkrate) + host->clkrate = clk_get_rate(host->clk_ciu); + host->mci.f_min = host->clkrate / 510 / host->ciu_div; if (host->mci.f_min < 200000) host->mci.f_min = 200000; @@ -625,6 +640,10 @@ static __maybe_unused struct of_device_id dw_mmc_compatible[] = { }, { .compatible = "rockchip,rk3288-dw-mshc", }, { + .compatible = "snps,dw-mshc", + }, { + .compatible = "starfive,jh7100-dw-mshc", + }, { /* sentinel */ } }; diff --git a/drivers/mci/mci-core.c b/drivers/mci/mci-core.c index a094f3cbf5..92a73c8f1d 100644 --- a/drivers/mci/mci-core.c +++ b/drivers/mci/mci-core.c @@ -21,6 +21,7 @@ #include <of.h> #include <linux/err.h> #include <linux/sizes.h> +#include <dma.h> #define MAX_BUFFER_NUMBER 0xffffffff @@ -1789,6 +1790,11 @@ static int mci_card_probe(struct mci *mci) mci->cdevname = basprintf("disk%d", disknum); } + if (!sector_buf) + sector_buf = dma_alloc(SECTOR_SIZE); + + /* FIXME we don't check sector_buf against the device dma mask here */ + rc = mci_startup(mci); if (rc) { dev_warn(&mci->dev, "Card's startup fails with %d\n", rc); @@ -1850,15 +1856,6 @@ static int mci_set_probe(struct param_d *param, void *priv) return 0; } -static int mci_init(void) -{ - sector_buf = xmemalign(32, SECTOR_SIZE); - - return 0; -} - -device_initcall(mci_init); - int mci_detect_card(struct mci_host *host) { int rc; diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 87674a2a29..7426dfc463 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -42,4 +42,14 @@ config ACPI_TEST help This is a simple Test driver to test the ACPI bus. +config STARFIVE_PWRSEQ + bool "StarFive power sequencing driver" + depends on SOC_STARFIVE + help + This driver sets up a number of StarFive peripherals not matched + by more specific barebox drivers by deasserting reset lines, muxing + pins and/or enabling clocks. Peripherals set up by this can then + be accessed over /dev/mem or used from kernels which still depend + on bootloader for initialization. + endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 4d92465a1e..36743e6ae6 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_STATE_DRV) += state.o obj-$(CONFIG_DEV_MEM) += mem.o obj-$(CONFIG_UBOOTVAR) += ubootvar.o obj-$(CONFIG_ACPI_TEST) += acpi-test.o +obj-$(CONFIG_STARFIVE_PWRSEQ) += starfive-pwrseq.o diff --git a/drivers/misc/starfive-pwrseq.c b/drivers/misc/starfive-pwrseq.c new file mode 100644 index 0000000000..6236547bc5 --- /dev/null +++ b/drivers/misc/starfive-pwrseq.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 Ahmad Fatoum, Pengutronix + */ + +#include <driver.h> +#include <init.h> +#include <linux/reset.h> +#include <dt-bindings/clock/starfive-jh7100.h> +#include <linux/clk.h> + +struct starfive_pwrseq { + const char **names; +}; + +static int starfive_pwrseq_probe(struct device_d *dev) +{ + int ret; + + ret = device_reset_all(dev); + if (ret) + return ret; + + return of_platform_populate(dev->device_node, NULL, dev); +} + +static struct of_device_id starfive_pwrseq_dt_ids[] = { + { .compatible = "starfive,axi-dma" }, + { .compatible = "cm,cm521-vpu" }, + { .compatible = "starfive,vic-sec" }, + { .compatible = "sfc,tempsensor" }, + { .compatible = "cm,codaj12-jpu-1" }, + { .compatible = "cdns,xrp" }, + { .compatible = "starfive,nne50" }, + { .compatible = "nvidia,nvdla_os_initial" }, + { .compatible = "starfive,spi2ahb" }, + { /* sentinel */ } +}; + +static struct driver_d starfive_pwrseq_driver = { + .name = "starfive_pwrseq", + .probe = starfive_pwrseq_probe, + .of_compatible = starfive_pwrseq_dt_ids, +}; + +static const int clks[] = { + CLK_VDEC_AXI, CLK_VDECBRG_MAIN, CLK_VDEC_BCLK, CLK_VDEC_CCLK, CLK_VDEC_APB, + CLK_JPEG_AXI, CLK_JPEG_CCLK, CLK_JPEG_APB, + CLK_DLA_AXI, CLK_DLANOC_AXI, CLK_DLA_APB, CLK_NNENOC_AXI, CLK_DLASLV_AXI, + CLK_VENC_AXI, CLK_VENCBRG_MAIN, CLK_VENC_BCLK, CLK_VENC_CCLK, CLK_VENC_APB, + CLK_SGDMA1P_AXI, + CLK_DMA2PNOC_AXI, CLK_SGDMA2P_AXI, CLK_SGDMA2P_AHB, + CLK_SDIO0_AHB, + CLK_SDIO1_AHB, + CLK_SPI2AHB_AHB, CLK_SPI2AHB_CORE, + CLK_EZMASTER_AHB, + CLK_SEC_AHB, CLK_AES, CLK_SHA, CLK_PKA, + CLK_UART0_APB, CLK_UART0_CORE, + CLK_UART1_APB, CLK_UART1_CORE, + CLK_UART2_APB, CLK_UART2_CORE, + CLK_UART3_APB, CLK_UART3_CORE, + CLK_SPI0_APB, CLK_SPI0_CORE, + CLK_SPI1_APB, CLK_SPI1_CORE, + CLK_SPI2_APB, CLK_SPI2_CORE, + CLK_SPI3_APB, CLK_SPI3_CORE, + CLK_I2C0_APB, CLK_I2C0_CORE, + CLK_I2C1_APB, CLK_I2C1_CORE, + CLK_I2C2_APB, CLK_I2C2_CORE, + CLK_I2C3_APB, CLK_I2C3_CORE, + CLK_VP6INTC_APB, + CLK_TEMP_APB, CLK_TEMP_SENSE, + + CLK_END +}; + +static int __init starfive_pwrseq_driver_register(void) +{ + struct of_phandle_args clkspec; + int i; + + clkspec.args_count = 1; + clkspec.np = of_find_compatible_node(NULL, NULL, "starfive,jh7100-clkgen"); + if (clkspec.np) { + for (i = 0; clks[i] != CLK_END; i++) { + clkspec.args[0] = clks[i]; + clk_enable(of_clk_get_from_provider(&clkspec)); + } + } + + return platform_driver_register(&starfive_pwrseq_driver); +} +device_initcall(starfive_pwrseq_driver_register); diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index b37017372f..4c90ad9757 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -5646,6 +5646,7 @@ int nand_scan_tail(struct nand_chip *chip) if (!ecc->write_oob) ecc->write_oob = nand_write_oob_syndrome; } else if (ecc->mode == NAND_ECC_HW_SYNDROME) { + WARN(1, "CONFIG_NAND_ECC_HW_SYNDROME not enabled\n"); ret = -ENOSYS; goto err_nand_manuf_cleanup; } diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 18931211b5..802169a86e 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -82,6 +82,14 @@ config DRIVER_NET_DESIGNWARE_SOCFPGA This option enables support for the Synopsys Designware Core Univesal MAC 10M/100M/1G ethernet IP on SoCFPGA. +config DRIVER_NET_DESIGNWARE_STARFIVE + bool "Designware Universal MAC ethernet driver for StarFive platforms" + depends on SOC_STARFIVE || COMPILE_TEST + select MFD_SYSCON + help + This option enables support for the Synopsys + Designware Core Univesal MAC 10M/100M/1G ethernet IP on StarFive. + endif config DRIVER_NET_DESIGNWARE_EQOS diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 1674d53dff..fb3e3bdee4 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_DRIVER_NET_DAVINCI_EMAC) += davinci_emac.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE) += designware.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_GENERIC) += designware_generic.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_SOCFPGA) += designware_socfpga.o +obj-$(CONFIG_DRIVER_NET_DESIGNWARE_STARFIVE) += designware_starfive.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_EQOS) += designware_eqos.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_STM32) += designware_stm32.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_TEGRA186) += designware_tegra186.o diff --git a/drivers/net/designware.c b/drivers/net/designware.c index 0ee6d3d78a..afc275e81e 100644 --- a/drivers/net/designware.c +++ b/drivers/net/designware.c @@ -104,15 +104,15 @@ static void tx_descs_init(struct eth_device *dev) { struct dw_eth_dev *priv = dev->priv; struct eth_dma_regs *dma_p = priv->dma_regs_p; - struct dmamacdescr *desc_table_p = &priv->tx_mac_descrtable[0]; + struct dmamacdescr *desc_table_p = &priv->tx_mac_descrtable_cpu[0]; char *txbuffs = &priv->txbuffs[0]; struct dmamacdescr *desc_p; u32 idx; for (idx = 0; idx < CONFIG_TX_DESCR_NUM; idx++) { desc_p = &desc_table_p[idx]; - desc_p->dmamac_addr = &txbuffs[idx * CONFIG_ETH_BUFSIZE]; - desc_p->dmamac_next = &desc_table_p[idx + 1]; + desc_p->dmamac_addr = virt_to_phys(&txbuffs[idx * CONFIG_ETH_BUFSIZE]); + desc_p->dmamac_next = tx_dma_addr(priv, &desc_table_p[idx + 1]); if (priv->enh_desc) { desc_p->txrx_status &= ~(DESC_ENH_TXSTS_TXINT | DESC_ENH_TXSTS_TXLAST | @@ -130,9 +130,9 @@ static void tx_descs_init(struct eth_device *dev) } /* Correcting the last pointer of the chain */ - desc_p->dmamac_next = &desc_table_p[0]; + desc_p->dmamac_next = tx_dma_addr(priv, &desc_table_p[0]); - writel((ulong)&desc_table_p[0], &dma_p->txdesclistaddr); + writel(desc_p->dmamac_next, &dma_p->txdesclistaddr); priv->tx_currdescnum = 0; } @@ -140,15 +140,15 @@ static void rx_descs_init(struct eth_device *dev) { struct dw_eth_dev *priv = dev->priv; struct eth_dma_regs *dma_p = priv->dma_regs_p; - struct dmamacdescr *desc_table_p = &priv->rx_mac_descrtable[0]; + struct dmamacdescr *desc_table_p = &priv->rx_mac_descrtable_cpu[0]; char *rxbuffs = &priv->rxbuffs[0]; struct dmamacdescr *desc_p; u32 idx; for (idx = 0; idx < CONFIG_RX_DESCR_NUM; idx++) { desc_p = &desc_table_p[idx]; - desc_p->dmamac_addr = &rxbuffs[idx * CONFIG_ETH_BUFSIZE]; - desc_p->dmamac_next = &desc_table_p[idx + 1]; + desc_p->dmamac_addr = virt_to_phys(&rxbuffs[idx * CONFIG_ETH_BUFSIZE]); + desc_p->dmamac_next = rx_dma_addr(priv, &desc_table_p[idx + 1]); desc_p->dmamac_cntl = MAC_MAX_FRAME_SZ; if (priv->enh_desc) @@ -156,15 +156,15 @@ static void rx_descs_init(struct eth_device *dev) else desc_p->dmamac_cntl |= DESC_RXCTRL_RXCHAIN; - dma_sync_single_for_cpu((unsigned long)desc_p->dmamac_addr, + dma_sync_single_for_cpu(desc_p->dmamac_addr, CONFIG_ETH_BUFSIZE, DMA_FROM_DEVICE); desc_p->txrx_status = DESC_RXSTS_OWNBYDMA; } /* Correcting the last pointer of the chain */ - desc_p->dmamac_next = &desc_table_p[0]; + desc_p->dmamac_next = rx_dma_addr(priv, &desc_table_p[0]); - writel((ulong)&desc_table_p[0], &dma_p->rxdesclistaddr); + writel(desc_p->dmamac_next, &dma_p->rxdesclistaddr); priv->rx_currdescnum = 0; } @@ -276,7 +276,7 @@ static int dwc_ether_send(struct eth_device *dev, void *packet, int length) struct dw_eth_dev *priv = dev->priv; struct eth_dma_regs *dma_p = priv->dma_regs_p; u32 owndma, desc_num = priv->tx_currdescnum; - struct dmamacdescr *desc_p = &priv->tx_mac_descrtable[desc_num]; + struct dmamacdescr *desc_p = &priv->tx_mac_descrtable_cpu[desc_num]; owndma = priv->enh_desc ? DESC_ENH_TXSTS_OWNBYDMA : DESC_TXSTS_OWNBYDMA; /* Check if the descriptor is owned by CPU */ @@ -285,8 +285,8 @@ static int dwc_ether_send(struct eth_device *dev, void *packet, int length) return -1; } - memcpy((void *)desc_p->dmamac_addr, packet, length); - dma_sync_single_for_device((unsigned long)desc_p->dmamac_addr, length, + memcpy(dmamac_addr(desc_p), packet, length); + dma_sync_single_for_device(desc_p->dmamac_addr, length, DMA_TO_DEVICE); if (priv->enh_desc) { @@ -314,7 +314,7 @@ static int dwc_ether_send(struct eth_device *dev, void *packet, int length) /* Start the transmission */ writel(POLL_DATA, &dma_p->txpolldemand); - dma_sync_single_for_cpu((unsigned long)desc_p->dmamac_addr, length, + dma_sync_single_for_cpu(desc_p->dmamac_addr, length, DMA_TO_DEVICE); return 0; @@ -324,7 +324,7 @@ static int dwc_ether_rx(struct eth_device *dev) { struct dw_eth_dev *priv = dev->priv; u32 desc_num = priv->rx_currdescnum; - struct dmamacdescr *desc_p = &priv->rx_mac_descrtable[desc_num]; + struct dmamacdescr *desc_p = &priv->rx_mac_descrtable_cpu[desc_num]; u32 status = desc_p->txrx_status; int length = 0; @@ -358,10 +358,10 @@ static int dwc_ether_rx(struct eth_device *dev) length = (status & DESC_RXSTS_FRMLENMSK) >> DESC_RXSTS_FRMLENSHFT; - dma_sync_single_for_cpu((unsigned long)desc_p->dmamac_addr, + dma_sync_single_for_cpu(desc_p->dmamac_addr, length, DMA_FROM_DEVICE); - net_receive(dev, desc_p->dmamac_addr, length); - dma_sync_single_for_device((unsigned long)desc_p->dmamac_addr, + net_receive(dev, dmamac_addr(desc_p), length); + dma_sync_single_for_device(desc_p->dmamac_addr, length, DMA_FROM_DEVICE); ret = length; } @@ -451,17 +451,20 @@ struct dw_eth_dev *dwc_drv_probe(struct device_d *dev) int ret; struct dw_eth_drvdata *drvdata; + dma_set_mask(dev, DMA_BIT_MASK(32)); + priv = xzalloc(sizeof(struct dw_eth_dev)); ret = dev_get_drvdata(dev, (const void **)&drvdata); if (ret) return ERR_PTR(ret); - if (drvdata && drvdata->enh_desc) + if (drvdata) { priv->enh_desc = drvdata->enh_desc; - else + priv->fix_mac_speed = drvdata->fix_mac_speed; + } else { dev_warn(dev, "No drvdata specified\n"); - + } if (pdata) { priv->phy_addr = pdata->phy_addr; @@ -481,12 +484,21 @@ struct dw_eth_dev *dwc_drv_probe(struct device_d *dev) priv->mac_regs_p = base; dwc_version(dev, readl(&priv->mac_regs_p->version)); priv->dma_regs_p = base + DW_DMA_BASE_OFFSET; - priv->tx_mac_descrtable = dma_alloc_coherent( + + priv->tx_mac_descrtable_cpu = dma_alloc_coherent( CONFIG_TX_DESCR_NUM * sizeof(struct dmamacdescr), - DMA_ADDRESS_BROKEN); - priv->rx_mac_descrtable = dma_alloc_coherent( + &priv->tx_mac_descrtable_dev); + + if (dma_mapping_error(dev, priv->tx_mac_descrtable_dev)) + return ERR_PTR(-EFAULT); + + priv->rx_mac_descrtable_cpu = dma_alloc_coherent( CONFIG_RX_DESCR_NUM * sizeof(struct dmamacdescr), - DMA_ADDRESS_BROKEN); + &priv->rx_mac_descrtable_dev); + + if (dma_mapping_error(dev, priv->rx_mac_descrtable_dev)) + return ERR_PTR(-EFAULT); + priv->txbuffs = dma_alloc(TX_TOTAL_BUFSIZE); priv->rxbuffs = dma_alloc(RX_TOTAL_BUFSIZE); diff --git a/drivers/net/designware.h b/drivers/net/designware.h index 0a6a6bf1a4..8f6234aec5 100644 --- a/drivers/net/designware.h +++ b/drivers/net/designware.h @@ -8,6 +8,7 @@ #define __DESIGNWARE_ETH_H #include <net.h> +#include <linux/types.h> struct dw_eth_dev { struct eth_device netdev; @@ -18,8 +19,11 @@ struct dw_eth_dev { u32 tx_currdescnum; u32 rx_currdescnum; - struct dmamacdescr *tx_mac_descrtable; - struct dmamacdescr *rx_mac_descrtable; + struct dmamacdescr *tx_mac_descrtable_cpu; + struct dmamacdescr *rx_mac_descrtable_cpu; + + dma_addr_t tx_mac_descrtable_dev; + dma_addr_t rx_mac_descrtable_dev; u8 *txbuffs; u8 *rxbuffs; @@ -35,9 +39,24 @@ struct dw_eth_dev { struct dw_eth_drvdata { bool enh_desc; + void (*fix_mac_speed)(int speed); void *priv; }; +static inline dma_addr_t tx_dma_addr(struct dw_eth_dev *priv, + struct dmamacdescr *desc) +{ + return priv->tx_mac_descrtable_dev + + ((u8 *)desc - (u8 *)priv->tx_mac_descrtable_cpu); +} + +static inline dma_addr_t rx_dma_addr(struct dw_eth_dev *priv, + struct dmamacdescr *desc) +{ + return priv->rx_mac_descrtable_dev + + ((u8 *)desc - (u8 *)priv->rx_mac_descrtable_cpu); +} + struct dw_eth_dev *dwc_drv_probe(struct device_d *dev); void dwc_drv_remove(struct device_d *dev); @@ -138,10 +157,12 @@ struct eth_dma_regs { struct dmamacdescr { u32 txrx_status; u32 dmamac_cntl; - void *dmamac_addr; - struct dmamacdescr *dmamac_next; + u32 dmamac_addr; + u32 dmamac_next; }; +#define dmamac_addr(descr) (phys_to_virt((descr)->dmamac_addr)) + /* * txrx_status definitions */ diff --git a/drivers/net/designware_starfive.c b/drivers/net/designware_starfive.c new file mode 100644 index 0000000000..3dc9d14e11 --- /dev/null +++ b/drivers/net/designware_starfive.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021 Ahmad Fatoum, Pengutronix + */ + +#include <common.h> +#include <init.h> +#include <linux/reset.h> +#include <linux/clk.h> +#include <mfd/syscon.h> +#include <soc/starfive/sysmain.h> +#include "designware.h" + +/* + * GMAC_GTXCLK + * bit name access default description + * [31] _gmac_gtxclk enable RW 0x0 "1:enable; 0:disable" + * [30] reserved - 0x0 reserved + * [29:8] reserved - 0x0 reserved + * [7:0] gmac_gtxclk ratio RW 0x4 divider value + * + * 1000M: gtxclk@125M => 500/125 = 0x4 + * 100M: gtxclk@25M => 500/25 = 0x14 + * 10M: gtxclk@2.5M => 500/2.5 = 0xc8 + */ + +#define CLKGEN_BASE 0x11800000 +#define CLKGEN_GMAC_GTXCLK_OFFSET 0x1EC +#define CLKGEN_GMAC_GTXCLK_ADDR (CLKGEN_BASE + CLKGEN_GMAC_GTXCLK_OFFSET) + + +#define CLKGEN_125M_DIV 0x4 +#define CLKGEN_25M_DIV 0x14 +#define CLKGEN_2_5M_DIV 0xc8 + +static void dwmac_fixed_speed(int speed) +{ + /* TODO: move this into clk driver */ + void __iomem *addr = IOMEM(CLKGEN_GMAC_GTXCLK_ADDR); + u32 value; + + value = readl(addr) & (~0x000000FF); + + switch (speed) { + case SPEED_1000: value |= CLKGEN_125M_DIV; break; + case SPEED_100: value |= CLKGEN_25M_DIV; break; + case SPEED_10: value |= CLKGEN_2_5M_DIV; break; + default: return; + } + + writel(value, addr); +} + +static struct dw_eth_drvdata starfive_drvdata = { + .enh_desc = 1, + .fix_mac_speed = dwmac_fixed_speed, +}; + +static int starfive_dwc_ether_probe(struct device_d *dev) +{ + struct dw_eth_dev *dwc; + struct regmap *regmap; + int ret; + struct clk_bulk_data clks[] = { + { .id = "stmmaceth" }, + { .id = "ptp_ref" }, + { .id = "tx" }, + }; + + regmap = syscon_regmap_lookup_by_phandle(dev->device_node, "starfive,sysmain"); + if (IS_ERR(regmap)) { + dev_err(dev, "Could not get starfive,sysmain node\n"); + return PTR_ERR(regmap); + } + + ret = clk_bulk_get(dev, ARRAY_SIZE(clks), clks); + if (ret) + return ret; + + ret = clk_bulk_enable(ARRAY_SIZE(clks), clks); + if (ret < 0) + return ret; + + ret = device_reset(dev); + if (ret) + return ret; + + dwc = dwc_drv_probe(dev); + if (IS_ERR(dwc)) + return PTR_ERR(dwc); + + if (phy_interface_mode_is_rgmii(dwc->interface)) { + regmap_update_bits(regmap, SYSMAIN_GMAC_PHY_INTF_SEL, 0x7, 0x1); + regmap_write(regmap, SYSMAIN_GMAC_GTXCLK_DLYCHAIN_SEL, 0x4); + } + + return 0; +} + +static struct of_device_id starfive_dwc_ether_compatible[] = { + { .compatible = "starfive,stmmac", .data = &starfive_drvdata }, + { /* sentinel */ } +}; + +static struct driver_d starfive_dwc_ether_driver = { + .name = "starfive-designware_eth", + .probe = starfive_dwc_ether_probe, + .of_compatible = starfive_dwc_ether_compatible, +}; +device_platform_driver(starfive_dwc_ether_driver); diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index e4a72b1431..3781f7a839 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -9,6 +9,12 @@ menuconfig NVMEM if NVMEM +config NVMEM_RMEM + bool "Reserved Memory Based Driver Support" + help + This driver maps reserved memory into an nvmem device. It might be + useful to expose information left by firmware in memory. + config NVMEM_SNVS_LPGPR tristate "Freescale SNVS LPGPR support" select MFD_SYSCON @@ -59,4 +65,12 @@ config STM32_BSEC This adds support for the STM32 OTP controller. Reads and writes to will go to the shadow RAM, not the OTP fuses themselvers. +config STARFIVE_OTP + tristate "Starfive OTP Supprot" + depends on SOC_STARFIVE + depends on OFDEVICE + help + This adds support for the StarFive OTP controller. Only reading + is currently supported. + endif diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 617e3725a7..55507f5441 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -3,7 +3,9 @@ # obj-$(CONFIG_NVMEM) += nvmem_core.o -nvmem_core-y := core.o regmap.o +nvmem_core-y := core.o regmap.o partition.o + +obj-$(CONFIG_NVMEM_RMEM) += rmem.o # Devices obj-$(CONFIG_NVMEM_SNVS_LPGPR) += nvmem_snvs_lpgpr.o @@ -20,3 +22,5 @@ nvmem_eeprom_93xx46-y := eeprom_93xx46.o obj-$(CONFIG_STM32_BSEC) += nvmem_bsec.o nvmem_bsec-y := bsec.o + +obj-$(CONFIG_STARFIVE_OTP) += starfive-otp.o diff --git a/drivers/nvmem/bsec.c b/drivers/nvmem/bsec.c index 509a5fa872..d9b38c8414 100644 --- a/drivers/nvmem/bsec.c +++ b/drivers/nvmem/bsec.c @@ -23,7 +23,6 @@ struct bsec_priv { u32 svc_id; struct regmap_config map_config; - struct nvmem_config config; }; struct stm32_bsec_data { diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index cfeecf70cd..4e558e1650 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -49,6 +49,15 @@ struct nvmem_cell { static LIST_HEAD(nvmem_cells); static LIST_HEAD(nvmem_devs); +void nvmem_devices_print(void) +{ + struct nvmem_device *dev; + + list_for_each_entry(dev, &nvmem_devs, node) { + printf("%s\n", dev_name(&dev->dev)); + } +} + static ssize_t nvmem_cdev_read(struct cdev *cdev, void *buf, size_t count, loff_t offset, unsigned long flags) { @@ -205,17 +214,17 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) nvmem->size = config->size; nvmem->dev.parent = config->dev; nvmem->bus = config->bus; - np = config->dev->device_node; + np = config->cdev ? config->cdev->device_node : config->dev->device_node; nvmem->dev.device_node = np; nvmem->priv = config->priv; - nvmem->read_only = of_property_read_bool(np, "read-only") | - config->read_only; + if (config->read_only || !config->bus->write || of_property_read_bool(np, "read-only")) + nvmem->read_only = true; dev_set_name(&nvmem->dev, config->name); nvmem->dev.id = DEVICE_ID_DYNAMIC; - dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); + dev_dbg(nvmem->dev.parent, "Registering nvmem device %s\n", config->name); rval = register_device(&nvmem->dev); if (rval) { @@ -223,10 +232,12 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) return ERR_PTR(rval); } - rval = nvmem_register_cdev(nvmem, config->name); - if (rval) { - kfree(nvmem); - return ERR_PTR(rval); + if (!config->cdev) { + rval = nvmem_register_cdev(nvmem, config->name); + if (rval) { + kfree(nvmem); + return ERR_PTR(rval); + } } list_add_tail(&nvmem->node, &nvmem_devs); diff --git a/drivers/nvmem/partition.c b/drivers/nvmem/partition.c new file mode 100644 index 0000000000..3f0bdc58de --- /dev/null +++ b/drivers/nvmem/partition.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <common.h> +#include <driver.h> +#include <malloc.h> +#include <xfuncs.h> +#include <errno.h> +#include <init.h> +#include <io.h> +#include <linux/nvmem-provider.h> + +static int nvmem_cdev_write(void *ctx, unsigned offset, const void *val, size_t bytes) +{ + return cdev_write(ctx, val, bytes, offset, 0); +} + +static int nvmem_cdev_read(void *ctx, unsigned offset, void *buf, size_t bytes) +{ + return cdev_read(ctx, buf, bytes, offset, 0); +} + +static struct nvmem_bus nvmem_cdev_bus = { + .read = nvmem_cdev_read, + .write = nvmem_cdev_write, +}; + +struct nvmem_device *nvmem_partition_register(struct cdev *cdev) +{ + struct nvmem_config config = {}; + + config.name = cdev->name; + config.dev = cdev->dev; + config.cdev = cdev; + config.priv = cdev; + config.stride = 1; + config.word_size = 1; + config.size = cdev->size; + config.bus = &nvmem_cdev_bus; + + return nvmem_register(&config); +} diff --git a/drivers/nvmem/rmem.c b/drivers/nvmem/rmem.c new file mode 100644 index 0000000000..e103cec448 --- /dev/null +++ b/drivers/nvmem/rmem.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> + */ + +#include <io.h> +#include <driver.h> +#include <linux/nvmem-provider.h> +#include <init.h> + +struct rmem { + struct device_d *dev; + const struct resource *mem; +}; + +static int rmem_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct rmem *rmem = context; + return mem_copy(rmem->dev, val, (void *)rmem->mem->start + offset, + bytes, offset, 0); +} + +static struct nvmem_bus rmem_nvmem_bus = { + .read = rmem_read, +}; + +static int rmem_probe(struct device_d *dev) +{ + struct nvmem_config config = { }; + struct resource *mem; + struct rmem *priv; + + mem = dev_request_mem_resource(dev, 0); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->mem = mem; + + config.dev = priv->dev = dev; + config.priv = priv; + config.name = "rmem"; + config.size = resource_size(mem); + config.bus = &rmem_nvmem_bus; + + return PTR_ERR_OR_ZERO(nvmem_register(&config)); +} + +static const struct of_device_id rmem_match[] = { + { .compatible = "nvmem-rmem", }, + { /* sentinel */ }, +}; + +static struct driver_d rmem_driver = { + .name = "rmem", + .of_compatible = rmem_match, + .probe = rmem_probe, +}; +device_platform_driver(rmem_driver); + +MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>"); +MODULE_DESCRIPTION("Reserved Memory Based nvmem Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/starfive-otp.c b/drivers/nvmem/starfive-otp.c new file mode 100644 index 0000000000..f9bf05ca87 --- /dev/null +++ b/drivers/nvmem/starfive-otp.c @@ -0,0 +1,201 @@ +// SPDX_License-Identifier: GPL-2.0 +/* + * Copyright 2021 StarFive, Inc + */ + +#include <common.h> +#include <driver.h> +#include <malloc.h> +#include <xfuncs.h> +#include <errno.h> +#include <gpiod.h> +#include <init.h> +#include <net.h> +#include <io.h> +#include <of.h> +#include <regmap.h> +#include <machine_id.h> +#include <linux/reset.h> +#include <linux/clk.h> +#include <linux/nvmem-provider.h> + +// otp reg offset +#define OTP_CFGR 0x00 +#define OTPC_IER 0x04 +#define OTPC_SRR 0x08 +#define OTP_OPRR 0x0c +#define OTPC_CTLR 0x10 +#define OTPC_ADDRR 0x14 +#define OTPC_DINR 0x18 +#define OTPC_DOUTR 0x1c + +#define OTP_EMPTY_CELL_VALUE 0xffffffffUL + +// cfgr (offset 0x00) +#define OTP_CFGR_PRG_CNT_MASK 0xff +#define OTP_CFGR_PRG_CNT_SHIFT 0 +#define OTP_CFGR_DIV_1US_MASK 0xff +#define OTP_CFGR_DIV_1US_SHIFT 8 +#define OTP_CFGR_RD_CYC_MASK 0x0f +#define OTP_CFGR_RD_CYC_SHIFT 16 + +// ier (offset 0x04) +#define OTPC_IER_DONE_IE BIT(0) +#define OTPC_IER_BUSY_OPR_IE BIT(1) + +// srr (offset 0x08) +#define OTPC_SRR_DONE BIT(0) +#define OTPC_SRR_BUSY_OPR BIT(1) +#define OTPC_SRR_INFO_RD_LOCK BIT(29) +#define OTPC_SRR_INFO_WR_LOCK BIT(30) +#define OTPC_SRR_BUSY BIT(31) + +// oprr (offset 0x0c) +#define OTP_OPRR_OPR_MASK 0x00000007 +#define OTP_OPRR_OPR_SHIFT 0 + +#define OTP_OPR_STANDBY 0x0 // user mode +#define OTP_OPR_READ 0x1 // user mode +#define OTP_OPR_MARGIN_READ_PROG 0x2 // testing mode +#define OTP_OPR_MARGIN_READ_INIT 0x3 // testing mode +#define OTP_OPR_PROGRAM 0x4 // user mode +#define OTP_OPR_DEEP_STANDBY 0x5 // user mode +#define OTP_OPR_DEBUG 0x6 // user mode + +// ctlr (offset 0x10, see EG512X32TH028CW01_v1.0.pdf "Pin Description") +#define OTPC_CTLR_PCE BIT(0) +#define OTPC_CTLR_PTM_MASK 0x0000000e +#define OTPC_CTLR_PTM_SHIFT 1 +#define OTPC_CTLR_PDSTB BIT(4) +#define OTPC_CTLR_PTR BIT(5) +#define OTPC_CTLR_PPROG BIT(6) +#define OTPC_CTLR_PWE BIT(7) +#define OTPC_CTLR_PCLK BIT(8) + +// addrr (offset 0x14) +#define OTPC_ADDRR_PA_MASK 0x000001ff +#define OTPC_ADDRR_PA_SHIFT 0 + +/* + * data format: + * struct starfive_otp_data{ + * char vendor[32]; + * uint64_t sn; + * uint8_t mac_addr[6]; + * uint8_t padding_0[2]; + * } + */ + +struct starfive_otp { + int power_gpio; + struct starfive_otp_regs __iomem *regs; +}; + +struct starfive_otp_regs { + /* TODO: add otp ememory_eg512x32 registers define */ + u32 otp_cfg; /* timing Register */ + u32 otpc_ie; /* interrupt Enable */ + u32 otpc_sr; /* status Register */ + u32 otp_opr; /* operation mode select Register */ + u32 otpc_ctl; /* otp control port */ + u32 otpc_addr; /* otp pa port */ + u32 otpc_din; /* otp pdin port */ + u32 otpc_dout; /* otp pdout */ + u32 reserved[504]; + u32 mem[512]; +}; + +/* + * offset and size are assumed aligned to the size of the fuses (32-bit). + */ +static int starfive_otp_read(void *ctx, unsigned offset, unsigned *val) +{ + struct starfive_otp *priv = ctx; + + gpio_set_active(priv->power_gpio, true); + mdelay(10); + + //otp set to read mode + writel(OTP_OPR_READ, &priv->regs->otp_opr); + mdelay(5); + + /* read all requested fuses */ + *val = readl(&priv->regs->mem[offset / 4]); + + gpio_set_active(priv->power_gpio, false); + mdelay(5); + + return 0; +} + +static int starfive_otp_write(void *ctx, unsigned offset, unsigned val) +{ + return -EOPNOTSUPP; +} + +static struct regmap_bus starfive_otp_regmap_bus = { + .reg_read = starfive_otp_read, + .reg_write = starfive_otp_write, +}; + +static int starfive_otp_probe(struct device_d *dev) +{ + struct starfive_otp *priv; + struct regmap_config config = {}; + struct resource *iores; + struct regmap *map; + struct clk *clk; + u32 total_fuses; + int ret; + + clk = clk_get(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + clk_enable(clk); + + ret = device_reset(dev); + if (ret) + return ret; + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + + ret = of_property_read_u32(dev->device_node, "fuse-count", &total_fuses); + if (ret < 0) { + dev_err(dev, "missing required fuse-count property\n"); + return ret; + } + + config.name = "starfive-otp"; + config.reg_bits = 32; + config.val_bits = 32; + config.reg_stride = 4; + config.max_register = total_fuses; + + priv = xzalloc(sizeof(*priv)); + + priv->regs = IOMEM(iores->start); + priv->power_gpio = gpiod_get(dev, "power", GPIOD_OUT_LOW); + if (priv->power_gpio < 0) + return priv->power_gpio; + + map = regmap_init(dev, &starfive_otp_regmap_bus, priv, &config); + if (IS_ERR(map)) + return PTR_ERR(map); + + return PTR_ERR_OR_ZERO(nvmem_regmap_register(map, "starfive-otp")); +} + +static struct of_device_id starfive_otp_dt_ids[] = { + { .compatible = "starfive,fu740-otp" }, + { /* sentinel */ } +}; + +static struct driver_d starfive_otp_driver = { + .name = "starfive_otp", + .probe = starfive_otp_probe, + .of_compatible = starfive_otp_dt_ids, +}; +device_platform_driver(starfive_otp_driver); diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 23be25d85d..e58fe50f70 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -49,6 +49,7 @@ config OF_BAREBOX_ENV_IN_FS config OF_OVERLAY select OFTREE + select FIRMWARE bool "Devicetree overlays" help Overlays allow to patch the devicetree. Unlike Linux, Barebox does diff --git a/drivers/of/base.c b/drivers/of/base.c index 193bae7fa0..42b8d24874 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -15,6 +15,7 @@ * GNU General Public License for more details. */ #include <common.h> +#include <deep-probe.h> #include <of.h> #include <of_address.h> #include <errno.h> @@ -1682,6 +1683,9 @@ int of_modalias_node(struct device_node *node, char *modalias, int len) } EXPORT_SYMBOL_GPL(of_modalias_node); +static struct device_node *of_chosen; +static const char *of_model; + struct device_node *of_get_root_node(void) { return root_node; @@ -1694,11 +1698,26 @@ int of_set_root_node(struct device_node *node) root_node = node; + of_chosen = of_find_node_by_path("/chosen"); + of_property_read_string(root_node, "model", &of_model); + + if (of_model) + barebox_set_model(of_model); + of_alias_scan(); return 0; } +static int barebox_of_populate(void) +{ + if (IS_ENABLED(CONFIG_OFDEVICE) && deep_probe_is_supported()) + return of_probe(); + + return 0; +} +of_populate_initcall(barebox_of_populate); + int barebox_register_of(struct device_node *root) { if (root_node) @@ -1707,8 +1726,11 @@ int barebox_register_of(struct device_node *root) of_set_root_node(root); of_fix_tree(root); - if (IS_ENABLED(CONFIG_OFDEVICE)) - return of_probe(); + if (IS_ENABLED(CONFIG_OFDEVICE)) { + of_clk_init(root, NULL); + if (!deep_probe_is_supported()) + return of_probe(); + } return 0; } @@ -1720,7 +1742,7 @@ int barebox_register_fdt(const void *dtb) if (root_node) return -EBUSY; - root = of_unflatten_dtb(dtb); + root = of_unflatten_dtb(dtb, INT_MAX); if (IS_ERR(root)) { pr_err("Cannot unflatten dtb: %pe\n", root); return PTR_ERR(root); @@ -2275,9 +2297,6 @@ int of_add_memory(struct device_node *node, bool dump) return ret; } -static struct device_node *of_chosen; -static const char *of_model; - const char *of_get_model(void) { return of_model; @@ -2300,6 +2319,9 @@ static int of_probe_memory(void) struct device_node *memory = root_node; int ret = 0; + if (!IS_ENABLED(CONFIG_OFDEVICE)) + return 0; + /* Parse all available node with "memory" device_type */ while (1) { int err; @@ -2315,6 +2337,7 @@ static int of_probe_memory(void) return ret; } +mem_initcall(of_probe_memory); static void of_platform_device_create_root(struct device_node *np) { @@ -2331,32 +2354,35 @@ static void of_platform_device_create_root(struct device_node *np) free(dev); } +static const struct of_device_id reserved_mem_matches[] = { + { .compatible = "nvmem-rmem" }, + {} +}; + int of_probe(void) { - struct device_node *firmware; - int ret; + struct device_node *node; if(!root_node) return -ENODEV; - of_chosen = of_find_node_by_path("/chosen"); - of_property_read_string(root_node, "model", &of_model); - - if (of_model) - barebox_set_model(of_model); - - ret = of_probe_memory(); + /* + * Handle certain compatibles explicitly, since we don't want to create + * platform_devices for every node in /reserved-memory with a + * "compatible", + */ + for_each_matching_node(node, reserved_mem_matches) + of_platform_device_create(node, NULL); - firmware = of_find_node_by_path("/firmware"); - if (firmware) - of_platform_populate(firmware, NULL, NULL); + node = of_find_node_by_path("/firmware"); + if (node) + of_platform_populate(node, NULL, NULL); of_platform_device_create_root(root_node); - of_clk_init(root_node, NULL); of_platform_populate(root_node, of_default_bus_match_table, NULL); - return ret; + return 0; } /** @@ -2409,6 +2435,7 @@ struct device_node *of_copy_node(struct device_node *parent, const struct device struct property *pp; np = of_new_node(parent, other->name); + np->phandle = other->phandle; list_for_each_entry(pp, &other->properties, list) of_new_property(np, pp->name, pp->value, pp->length); @@ -2419,15 +2446,24 @@ struct device_node *of_copy_node(struct device_node *parent, const struct device return np; } +struct device_node *of_dup(const struct device_node *root) +{ + return of_copy_node(NULL, root); +} + void of_delete_node(struct device_node *node) { struct device_node *n, *nt; struct property *p, *pt; - struct device_d *dev; if (!node) return; + if (node == root_node) { + pr_err("Won't delete root device node\n"); + return; + } + list_for_each_entry_safe(p, pt, &node->properties, list) of_delete_property(p); @@ -2439,28 +2475,18 @@ void of_delete_node(struct device_node *node) list_del(&node->list); } - dev = of_find_device_by_node(node); - if (dev) - dev->device_node = NULL; - free(node->name); free(node->full_name); free(node); - - if (node == root_node) - of_set_root_node(NULL); } -int of_device_is_stdout_path(struct device_d *dev) +struct device_node *of_get_stdoutpath(unsigned int *baudrate) { struct device_node *dn; const char *name; const char *p; char *q; - if (!dev->device_node) - return 0; - name = of_get_property(of_chosen, "stdout-path", NULL); if (!name) name = of_get_property(of_chosen, "linux,stdout-path", NULL); @@ -2468,10 +2494,7 @@ int of_device_is_stdout_path(struct device_d *dev) if (!name) return 0; - /* This could make use of strchrnul if it were available */ - p = strchr(name, ':'); - if (!p) - p = name + strlen(name); + p = strchrnul(name, ':'); q = xstrndup(name, p - name); @@ -2479,7 +2502,24 @@ int of_device_is_stdout_path(struct device_d *dev) free(q); - return dn == dev->device_node; + if (baudrate && *p) { + unsigned rate = simple_strtoul(p + 1, NULL, 10); + if (rate) + *baudrate = rate; + } + + return dn; +} + +int of_device_is_stdout_path(struct device_d *dev, unsigned int *baudrate) +{ + unsigned int tmp = *baudrate; + + if (!dev || !dev->device_node || dev->device_node != of_get_stdoutpath(&tmp)) + return false; + + *baudrate = tmp; + return true; } /** diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index d98913e54a..f72f5e3a30 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -114,7 +114,8 @@ static int of_unflatten_reservemap(struct device_node *root, * Parse a flat device tree binary blob and return a pointer to the * unflattened tree. */ -static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops) +static struct device_node *__of_unflatten_dtb(const void *infdt, int size, + bool constprops) { const void *nodep; /* property node pointer */ uint32_t tag; /* tag */ @@ -131,6 +132,9 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops unsigned int maxlen; const struct fdt_header *fdt = infdt; + if (size < sizeof(struct fdt_header)) + return ERR_PTR(-EINVAL); + if (fdt->magic != cpu_to_fdt32(FDT_MAGIC)) { pr_err("bad magic: 0x%08x\n", fdt32_to_cpu(fdt->magic)); return ERR_PTR(-EINVAL); @@ -147,6 +151,9 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops f.off_dt_strings = fdt32_to_cpu(fdt->off_dt_strings); f.size_dt_strings = fdt32_to_cpu(fdt->size_dt_strings); + if (f.totalsize > size) + return ERR_PTR(-EINVAL); + if (f.off_dt_struct + f.size_dt_struct > f.totalsize) { pr_err("unflatten: dt size exceeds total size\n"); return ERR_PTR(-ESPIPE); @@ -274,9 +281,9 @@ err: * Parse a flat device tree binary blob and return a pointer to the unflattened * tree. The tree must be freed after use with of_delete_node(). */ -struct device_node *of_unflatten_dtb(const void *infdt) +struct device_node *of_unflatten_dtb(const void *infdt, int size) { - return __of_unflatten_dtb(infdt, false); + return __of_unflatten_dtb(infdt, size, false); } /** @@ -290,9 +297,9 @@ struct device_node *of_unflatten_dtb(const void *infdt) * whole lifetime of the returned tree. This is normally not what you want, so * use of_unflatten_dtb() instead. */ -struct device_node *of_unflatten_dtb_const(const void *infdt) +struct device_node *of_unflatten_dtb_const(const void *infdt, int size) { - return __of_unflatten_dtb(infdt, true); + return __of_unflatten_dtb(infdt, size, true); } struct fdt { diff --git a/drivers/of/of_firmware.c b/drivers/of/of_firmware.c index 096f84572e..687e675302 100644 --- a/drivers/of/of_firmware.c +++ b/drivers/of/of_firmware.c @@ -6,34 +6,34 @@ #include <firmware.h> #include <of.h> -struct overlay_info { - const char *firmware_path; -}; - static struct firmware_mgr *of_node_get_mgr(struct device_node *np) { struct device_node *mgr_node; do { - if (of_device_is_compatible(np, "fpga-region")) { - mgr_node = of_parse_phandle(np, "fpga-mgr", 0); - if (mgr_node) - return firmwaremgr_find_by_node(mgr_node); - } + mgr_node = of_parse_phandle(np, "fpga-mgr", 0); + if (mgr_node) + return firmwaremgr_find_by_node(mgr_node); } while ((np = of_get_parent(np)) != NULL); return NULL; } +struct fw_load_entry { + struct firmware_mgr *mgr; + char *firmware; + struct list_head list; +}; + +static LIST_HEAD(fw_load_list); + static int load_firmware(struct device_node *target, - struct device_node *fragment, void *data) + struct device_node *fragment, void *unused) { - struct overlay_info *info = data; const char *firmware_name; - const char *firmware_path = info->firmware_path; - char *firmware; int err; struct firmware_mgr *mgr; + struct fw_load_entry *fle; err = of_property_read_string(fragment, "firmware-name", &firmware_name); @@ -46,46 +46,93 @@ static int load_firmware(struct device_node *target, if (!target) return -EINVAL; + if (!of_device_is_compatible(target, "fpga-region")) + return 0; + mgr = of_node_get_mgr(target); if (!mgr) return -EINVAL; - firmware = basprintf("%s/%s", firmware_path, firmware_name); - if (!firmware) - return -ENOMEM; + fle = xzalloc(sizeof(*fle)); + fle->mgr = mgr; + fle->firmware = xstrdup(firmware_name); - err = firmwaremgr_load_file(mgr, firmware); + list_add_tail(&fle->list, &fw_load_list); - free(firmware); + return 0; +} - return err; +/* + * The dt overlay API says that a "firmware-name" property found in an overlay + * node compatible to "fpga-region" triggers loading of the firmware with the + * name given in the "firmware-name" property. + * + * barebox applies overlays to the Kernel device tree as part of booting the + * Kernel. When a firmware is needed for an overlay then it shall be loaded, + * so that the Kernel already finds the firmware loaded. + * + * In barebox overlays are applied as a of_fixup to the desired tree. The fixups + * might be executed multiple times not only as part of booting the Kernel, but + * also during of_diff command execution and other actions. It's not desired + * that we (re-)load all firmwares each time this happens, so the process is + * splitted up. During application of an overlay the needed firmwares are only + * collected to a list, but not actually loaded. Only once it's clear we want to + * boot with that device tree the firmwares are loaded by explicitly calling + * of_overlay_load_firmware(). + */ + +/** + * of_overlay_pre_load_firmware() - check overlay node for firmware to load + * @root: The device tree to apply the overlay to + * @overlay: The overlay + * + * This function checks the given overlay for firmware to load. If a firmware + * is needed then it is not directly loaded, but instead added to a list of + * firmware to be loaded. The firmware files on this list can then be loaded + * with of_overlay_load_firmware(). + * + * Return: 0 for success or negative error code otherwise + */ +int of_overlay_pre_load_firmware(struct device_node *root, struct device_node *overlay) +{ + return of_process_overlay(root, overlay, load_firmware, NULL); } -int of_firmware_load_overlay(struct device_node *overlay, const char *path) +/** + * of_overlay_load_firmware() - load all firmware files + * + * This function loads all firmware files previously collected in + * of_overlay_pre_load_firmware(). + * + * Return: 0 when all firmware files could be loaded, negative error code + * otherwise. + */ +int of_overlay_load_firmware(void) { - struct overlay_info info = { - .firmware_path = path, - }; - int err; - struct device_node *root; - struct device_node *resolved; - struct device_node *ovl; - - root = of_get_root_node(); - resolved = of_resolve_phandles(root, overlay); - /* - * If the overlay cannot be resolved, use the load_firmware callback - * with the unresolved overlay to verify that the overlay does not - * depend on a firmware to be loaded. If a required firmware cannot be - * loaded, the overlay must not be applied. - */ - ovl = resolved ? resolved : overlay; - - err = of_process_overlay(root, ovl, - load_firmware, &info); - - if (resolved) - of_delete_node(resolved); - - return err; + struct fw_load_entry *fle; + int ret; + + list_for_each_entry(fle, &fw_load_list, list) { + ret = firmwaremgr_load_file(fle->mgr, fle->firmware); + if (ret) + return ret; + } + + return 0; +} + +/** + * of_overlay_load_firmware_clear() - Clear list of firmware files + * + * This function clears the list of firmware files. + */ +void of_overlay_load_firmware_clear(void) +{ + struct fw_load_entry *fle, *tmp; + + list_for_each_entry_safe(fle, tmp, &fw_load_list, list) { + list_del(&fle->list); + free(fle->firmware); + free(fle); + } } diff --git a/drivers/of/of_net.c b/drivers/of/of_net.c index cee4597195..67015160e2 100644 --- a/drivers/of/of_net.c +++ b/drivers/of/of_net.c @@ -9,6 +9,7 @@ #include <net.h> #include <of_net.h> #include <linux/phy.h> +#include <linux/nvmem-consumer.h> /** * It maps 'enum phy_interface_t' found in include/linux/phy.h @@ -67,12 +68,55 @@ int of_get_phy_mode(struct device_node *np) } EXPORT_SYMBOL_GPL(of_get_phy_mode); +static int of_get_mac_addr(struct device_node *np, const char *name, u8 *addr) +{ + struct property *pp = of_find_property(np, name, NULL); + + if (pp && pp->length == ETH_ALEN && is_valid_ether_addr(pp->value)) { + memcpy(addr, pp->value, ETH_ALEN); + return 0; + } + return -ENODEV; +} + +int of_get_mac_addr_nvmem(struct device_node *np, u8 *addr) +{ + struct nvmem_cell *cell; + const void *mac; + size_t len; + + if (!IS_ENABLED(CONFIG_NVMEM)) + return -ENODEV; + + cell = of_nvmem_cell_get(np, "mac-address"); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + mac = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + + if (IS_ERR(mac)) + return PTR_ERR(mac); + + if (len != ETH_ALEN || !is_valid_ether_addr(mac)) { + kfree(mac); + return -EINVAL; + } + + memcpy(addr, mac, ETH_ALEN); + kfree(mac); + + return 0; +} + /** * Search the device tree for the best MAC address to use. 'mac-address' is * checked first, because that is supposed to contain to "most recent" MAC * address. If that isn't set, then 'local-mac-address' is checked next, - * because that is the default address. If that isn't set, then the obsolete - * 'address' is checked, just in case we're using an old device tree. + * because that is the default address. If that isn't set, then the obsolete + * 'address' is checked, just in case we're using an old device tree. If any + * of the above isn't set, then try to get MAC address from nvmem cell named + * 'mac-address'. * * Note that the 'address' property is supposed to contain a virtual address of * the register set, but some DTS files have redefined that property to be the @@ -85,18 +129,24 @@ EXPORT_SYMBOL_GPL(of_get_phy_mode); * this case, the real MAC is in 'local-mac-address', and 'mac-address' exists * but is all zeros. */ -const void *of_get_mac_address(struct device_node *np) +int of_get_mac_address(struct device_node *np, u8 *addr) { - const void *p; - int len, i; - const char *str[] = { "mac-address", "local-mac-address", "address" }; - - for (i = 0; i < ARRAY_SIZE(str); i++) { - p = of_get_property(np, str[i], &len); - if (p && (len == 6) && is_valid_ether_addr(p)) - return p; - } + int ret; + + if (!np) + return -ENODEV; + + ret = of_get_mac_addr(np, "mac-address", addr); + if (!ret) + return 0; + + ret = of_get_mac_addr(np, "local-mac-address", addr); + if (!ret) + return 0; + + ret = of_get_mac_addr(np, "address", addr); + if (!ret) + return 0; - return NULL; + return of_get_mac_addr_nvmem(np, addr); } -EXPORT_SYMBOL(of_get_mac_address); diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 8f4ee3f0a2..42b309805f 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -11,6 +11,13 @@ #include <common.h> #include <of.h> #include <errno.h> +#include <globalvar.h> +#include <magicvar.h> +#include <string.h> +#include <libfile.h> +#include <fs.h> +#include <libbb.h> +#include <fnmatch.h> static struct device_node *find_target(struct device_node *root, struct device_node *fragment) @@ -160,6 +167,8 @@ static int of_overlay_apply_fragment(struct device_node *root, return of_overlay_apply(target, overlay); } +static char *of_overlay_compatible; + /** * Apply the overlay on the passed devicetree root * @root: the devicetree onto which the overlay will be applied @@ -176,6 +185,10 @@ int of_overlay_apply_tree(struct device_node *root, if (!resolved) return -EINVAL; + err = of_overlay_pre_load_firmware(root, resolved); + if (err) + goto out_err; + /* Copy symbols from resolved overlay to base device tree */ of_overlay_apply_symbols(root, resolved); @@ -186,11 +199,140 @@ int of_overlay_apply_tree(struct device_node *root, pr_warn("failed to apply %s\n", fragment->name); } +out_err: of_delete_node(resolved); return err; } +static char *of_overlay_filter; + +static LIST_HEAD(of_overlay_filters); + +static struct of_overlay_filter *of_overlay_find_filter(const char *name) +{ + struct of_overlay_filter *f; + + list_for_each_entry(f, &of_overlay_filters, list) + if (!strcmp(f->name, name)) + return f; + return NULL; +} + +static bool of_overlay_matches_filter(const char *filename, struct device_node *ovl) +{ + struct of_overlay_filter *filter; + char *p, *path, *n; + bool apply = false; + bool have_filename_filter = false; + bool have_content_filter = false; + + p = path = strdup(of_overlay_filter); + + while ((n = strsep_unescaped(&p, " "))) { + int score = 0; + + if (!*n) + continue; + + filter = of_overlay_find_filter(n); + if (!filter) { + pr_err("Ignoring unknown filter %s\n", n); + continue; + } + + if (filter->filter_filename) + have_filename_filter = true; + if (filter->filter_content) + have_content_filter = true; + + if (filename) { + if (filter->filter_filename && + filter->filter_filename(filter, kbasename(filename))) + score++; + } else { + score++; + } + + if (ovl) { + if (filter->filter_content && + filter->filter_content(filter, ovl)) + score++; + } else { + score++; + } + + if (score == 2) { + apply = true; + break; + } + } + + free(path); + + /* No filter found at all, no match */ + if (!have_filename_filter && !have_content_filter) + return false; + + /* Want to match filename, but we do not have a filename_filter */ + if (filename && !have_filename_filter) + return true; + + /* Want to match content, but we do not have a content_filter */ + if (ovl && !have_content_filter) + return true; + + if (apply) + pr_debug("filename %s, overlay %p: match against filter %s\n", + filename ?: "<NONE>", + ovl, filter->name); + else + pr_debug("filename %s, overlay %p: no match\n", + filename ?: "<NONE>", ovl); + + return apply; +} + +int of_overlay_apply_file(struct device_node *root, const char *filename, + bool filter) +{ + void *fdt; + struct device_node *ovl; + size_t size; + int ret; + + if (filter && !of_overlay_matches_filter(filename, NULL)) + return 0; + + ret = read_file_2(filename, &size, &fdt, FILESIZE_MAX); + if (ret) + return ret; + + ovl = of_unflatten_dtb(fdt, size); + + free(fdt); + + if (IS_ERR(ovl)) { + pr_err("Failed to unflatten %s: %pe\n", filename, ovl); + return PTR_ERR(ovl); + } + + if (filter && !of_overlay_matches_filter(NULL, ovl)) + return 0; + + ret = of_overlay_apply_tree(root, ovl); + if (ret == -ENODEV) + pr_debug("Not applied %s (not compatible)\n", filename); + else if (ret) + pr_err("Cannot apply %s: %s\n", filename, strerror(-ret)); + else + pr_info("Applied %s\n", filename); + + of_delete_node(ovl); + + return ret; +} + static int of_overlay_fixup(struct device_node *root, void *data) { struct device_node *overlay = data; @@ -250,3 +392,216 @@ int of_register_overlay(struct device_node *overlay) { return of_register_fixup(of_overlay_fixup, overlay); } + +static char *of_overlay_filepattern; +static char *of_overlay_dir; +static char *of_overlay_basedir; + +/** + * of_overlay_set_basedir - set the overlay basedir + * @path: The new overlay basedir + * + * This specifies the base directory where overlay files are expected. By + * default this is the root directory, but it is overwritten by blspec to + * point to the rootfs of the about-to-be-booted system. + */ +void of_overlay_set_basedir(const char *path) +{ + free(of_overlay_basedir); + of_overlay_basedir = strdup(path); +} + +static int of_overlay_apply_dir(struct device_node *root, const char *dirname, + bool filter) +{ + char *p, *path; + int ret = 0; + DIR *dir; + + if (!dirname || !*dirname) + return 0; + + pr_debug("Applying overlays from %s\n", dirname); + + dir = opendir(dirname); + if (!dir) + return -errno; + + p = path = strdup(of_overlay_filepattern); + + while (1) { + struct dirent *ent; + char *filename; + + ent = readdir(dir); + if (!ent) + break; + + if (!strcmp(dir->d.d_name, ".") || !strcmp(dir->d.d_name, "..")) + continue; + + filename = basprintf("%s/%s", dirname, dir->d.d_name); + + of_overlay_apply_file(root, filename, filter); + + free(filename); + } + + closedir(dir); + + return ret; +} + +static int of_overlay_global_fixup(struct device_node *root, void *data) +{ + char *dir; + int ret; + + if (*of_overlay_dir == '/') + return of_overlay_apply_dir(root, of_overlay_dir, true); + + dir = concat_path_file(of_overlay_basedir, of_overlay_dir); + + ret = of_overlay_apply_dir(root, dir, true); + + free(dir); + + return ret; +} + +/** + * of_overlay_register_filter - register a new overlay filter + * @filter: The new filter + * + * Register a new overlay filter. A filter can either match on + * the filename or on the content of an overlay, but not on both. + * If that's desired two filters have to be registered. + * + * @return: 0 for success, negative error code otherwise + */ +int of_overlay_register_filter(struct of_overlay_filter *filter) +{ + if (filter->filter_filename && filter->filter_content) + return -EINVAL; + + list_add_tail(&filter->list, &of_overlay_filters); + + return 0; +} + +/** + * of_overlay_filter_filename - A filter that matches on the filename of + * an overlay + * @f: The filter + * @filename: The filename of the overlay + * + * This filter matches when the filename matches one of the patterns given + * in global.of.overlay.filepattern. global.of.overlay.filepattern shall + * contain a space separated list of wildcard patterns. + * + * @return: True when the overlay shall be applied, false otherwise. + */ +static bool of_overlay_filter_filename(struct of_overlay_filter *f, + const char *filename) +{ + char *p, *path, *n; + int ret; + bool apply; + + p = path = strdup(of_overlay_filepattern); + + while ((n = strsep_unescaped(&p, " "))) { + if (!*n) + continue; + + ret = fnmatch(n, filename, 0); + + if (!ret) { + apply = true; + goto out; + } + } + + apply = false; +out: + free(path); + + return apply; +} + +static struct of_overlay_filter of_overlay_filepattern_filter = { + .name = "filepattern", + .filter_filename = of_overlay_filter_filename, +}; + +/** + * of_overlay_filter_compatible - A filter that matches on the compatible of + * an overlay + * @f: The filter + * @ovl: The overlay + * + * This filter matches when the compatible of an overlay matches to one + * of the compatibles given in global.of.overlay.compatible. When the + * overlay doesn't contain a compatible entry it is considered matching. + * Also when no compatibles are given in global.of.overlay.compatible + * all overlays will match. + * + * @return: True when the overlay shall be applied, false otherwise. + */ +static bool of_overlay_filter_compatible(struct of_overlay_filter *f, + struct device_node *ovl) +{ + char *p, *n, *compatibles; + bool res = false; + + if (!of_overlay_compatible || !*of_overlay_compatible) + return true; + if (!of_find_property(ovl, "compatible", NULL)) + return true; + + p = compatibles = xstrdup(of_overlay_compatible); + + while ((n = strsep_unescaped(&p, " "))) { + if (!*n) + continue; + + if (of_device_is_compatible(ovl, n)) { + res = true; + break; + } + } + + free(compatibles); + + return res; +} + +static struct of_overlay_filter of_overlay_compatible_filter = { + .name = "compatible", + .filter_content = of_overlay_filter_compatible, +}; + +static int of_overlay_init(void) +{ + of_overlay_filepattern = strdup("*"); + of_overlay_filter = strdup("filepattern compatible"); + of_overlay_set_basedir("/"); + + globalvar_add_simple_string("of.overlay.compatible", &of_overlay_compatible); + globalvar_add_simple_string("of.overlay.filepattern", &of_overlay_filepattern); + globalvar_add_simple_string("of.overlay.filter", &of_overlay_filter); + globalvar_add_simple_string("of.overlay.dir", &of_overlay_dir); + + of_overlay_register_filter(&of_overlay_filepattern_filter); + of_overlay_register_filter(&of_overlay_compatible_filter); + + of_register_fixup(of_overlay_global_fixup, NULL); + + return 0; +} +device_initcall(of_overlay_init); + +BAREBOX_MAGICVAR(global.of.overlay.compatible, "space separated list of compatibles an overlay must match"); +BAREBOX_MAGICVAR(global.of.overlay.filepattern, "space separated list of filepatterns an overlay must match"); +BAREBOX_MAGICVAR(global.of.overlay.dir, "Directory to look for dt overlays"); +BAREBOX_MAGICVAR(global.of.overlay.filter, "space separated list of filters"); diff --git a/drivers/of/partition.c b/drivers/of/partition.c index b71716218b..b6d0523fd9 100644 --- a/drivers/of/partition.c +++ b/drivers/of/partition.c @@ -20,6 +20,7 @@ #include <linux/mtd/mtd.h> #include <linux/err.h> #include <nand.h> +#include <linux/nvmem-provider.h> #include <init.h> #include <globalvar.h> @@ -83,6 +84,12 @@ struct cdev *of_parse_partition(struct cdev *cdev, struct device_node *node) if (new) new->device_node = node;; + if (IS_ENABLED(CONFIG_NVMEM) && of_device_is_compatible(node, "nvmem-cells")) { + struct nvmem_device *nvmem = nvmem_partition_register(new); + if (IS_ERR(nvmem)) + dev_warn(cdev->dev, "nvmem registeration failed: %pe\n", nvmem); + } + free(filename); return new; diff --git a/drivers/of/platform.c b/drivers/of/platform.c index 21c7cce1a5..92b6caef5a 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -15,6 +15,7 @@ * GNU General Public License for more details. */ #include <common.h> +#include <deep-probe.h> #include <malloc.h> #include <of.h> #include <of_address.h> @@ -29,6 +30,12 @@ struct device_d *of_find_device_by_node(struct device_node *np) { struct device_d *dev; + int ret; + + ret = of_device_ensure_probed(np); + if (ret) + return NULL; + for_each_device(dev) if (dev->device_node == np) return dev; @@ -101,11 +108,18 @@ struct device_d *of_platform_device_create(struct device_node *np, struct device_d *dev; struct resource *res = NULL, temp_res; resource_size_t resinval; - int i, j, ret, num_reg = 0, match; + int i, ret, num_reg = 0; if (!of_device_is_available(np)) return NULL; + /* + * Linux uses the OF_POPULATED flag to skip already populated/created + * devices. + */ + if (np->dev) + return np->dev; + /* count the io resources */ if (of_can_translate_address(np)) while (of_address_to_resource(np, num_reg, &temp_res) == 0) @@ -121,35 +135,6 @@ struct device_d *of_platform_device_create(struct device_node *np, return NULL; } } - - /* - * A device may already be registered as platform_device. - * Instead of registering the same device again, just - * add this node to the existing device. - */ - for_each_device(dev) { - if (!dev->resource) - continue; - - for (i = 0, match = 0; i < num_reg; i++) - for (j = 0; j < dev->num_resources; j++) - if (dev->resource[j].start == - res[i].start && - dev->resource[j].end == - res[i].end) { - match++; - break; - } - - /* check if all address resources match */ - if (match == num_reg) { - debug("connecting %s to %s\n", - np->name, dev_name(dev)); - dev->device_node = np; - free(res); - return dev; - } - } } /* setup generic device info */ @@ -169,16 +154,30 @@ struct device_d *of_platform_device_create(struct device_node *np, __func__, dev_name(dev), (num_reg) ? &dev->resource[0].start : &resinval); + BUG_ON(np->dev); + np->dev = dev; + ret = platform_device_register(dev); if (!ret) return dev; + np->dev = NULL; + free(dev); if (num_reg) free(res); return NULL; } +struct driver_d dummy_driver = { + .name = "dummy-driver", +}; + +void of_platform_device_dummy_drv(struct device_d *dev) +{ + dev->driver = &dummy_driver; +} + /** * of_device_enable_and_register - Enable and register device * @np: pointer to node to enable create device for @@ -252,6 +251,13 @@ static struct device_d *of_amba_device_create(struct device_node *np) if (!of_device_is_available(np)) return NULL; + /* + * Linux uses the OF_POPULATED flag to skip already populated/created + * devices. + */ + if (np->dev) + return np->dev; + dev = xzalloc(sizeof(*dev)); /* setup generic device info */ @@ -275,6 +281,8 @@ static struct device_d *of_amba_device_create(struct device_node *np) if (ret) goto amba_err_free; + np->dev = &dev->dev; + return &dev->dev; amba_err_free: @@ -364,3 +372,175 @@ int of_platform_populate(struct device_node *root, return rc; } EXPORT_SYMBOL_GPL(of_platform_populate); + +static struct device_d *of_device_create_on_demand(struct device_node *np) +{ + struct device_node *parent; + struct device_d *parent_dev, *dev; + + parent = of_get_parent(np); + if (!parent) + return NULL; + + if (!np->dev) + pr_debug("Creating device for %s\n", np->full_name); + + /* Create all parent devices needed for the requested device */ + parent_dev = parent->dev ? : of_device_create_on_demand(parent); + if (IS_ERR(parent_dev)) + return parent_dev; + + /* + * Parent devices like i2c/spi controllers are populating their own + * devices. So it can be that the requested device already exists after + * the parent device creation. + */ + if (np->dev) + return np->dev; + + if (of_device_is_compatible(np, "arm,primecell")) + dev = of_amba_device_create(np); + else + dev = of_platform_device_create(np, parent_dev); + + return dev ? : ERR_PTR(-ENODEV); +} + +/** + * of_device_ensure_probed() - ensures that a device is probed + * + * @np: the device_node handle which should be probed + * + * Ensures that the device is populated and probed so frameworks can make use of + * it. + * + * Return: %0 on success + * %-ENODEV if either the device can't be populated, the driver is + * missing or the driver probe returns an error. + */ +int of_device_ensure_probed(struct device_node *np) +{ + struct device_d *dev; + + if (!deep_probe_is_supported()) + return 0; + + dev = of_device_create_on_demand(np); + if (IS_ERR(dev)) + return PTR_ERR(dev); + + BUG_ON(!dev); + + /* + * The deep-probe mechanism relies on the fact that all necessary + * drivers are added before the device creation. Furthermore deep-probe + * is the answer to the EPROBE_DEFER errno so we must ensure that the + * driver was probed successfully after the device creation. Both + * requirements are fulfilled if 'dev->driver' is not NULL. + */ + if (!dev->driver) + return -ENODEV; + + return 0; +} +EXPORT_SYMBOL_GPL(of_device_ensure_probed); + +/** + * of_device_ensure_probed_by_alias() - ensures that a device is probed + * + * @alias: the alias string to search for a device + * + * The function search for a given alias string and ensures that the device is + * populated and probed if found. + * + * Return: %0 on success + * %-ENODEV if either the device can't be populated, the driver is + * missing or the driver probe returns an error + * %-EINVAL if alias can't be found + */ +int of_device_ensure_probed_by_alias(const char *alias) +{ + struct device_node *dev_node; + + dev_node = of_find_node_by_alias(NULL, alias); + if (!dev_node) + return -EINVAL; + + return of_device_ensure_probed(dev_node); +} +EXPORT_SYMBOL_GPL(of_device_ensure_probed_by_alias); + +/** + * of_devices_ensure_probed_by_dev_id() - ensures that devices are probed + * + * @ids: the matching 'struct of_device_id' ids + * + * The function start searching the device tree from @np and populates and + * probes devices which match @ids. + * + * Return: %0 on success + * %-ENODEV if either the device wasn't found, can't be populated, + * the driver is missing or the driver probe returns an error + */ +int of_devices_ensure_probed_by_dev_id(const struct of_device_id *ids) +{ + struct device_node *np; + int err, ret = 0; + + for_each_matching_node(np, ids) { + if (!of_device_is_available(np)) + continue; + + err = of_device_ensure_probed(np); + if (err) + ret = err; + } + + return ret; +} +EXPORT_SYMBOL_GPL(of_devices_ensure_probed_by_dev_id); + +/** + * of_devices_ensure_probed_by_property() - ensures that devices are probed + * + * @property_name: The property name to search for + * + * The function starts searching the whole device tree and populates and probes + * devices which matches @property_name. + * + * Return: %0 on success + * %-ENODEV if either the device wasn't found, can't be populated, + * the driver is missing or the driver probe returns an error + */ +int of_devices_ensure_probed_by_property(const char *property_name) +{ + struct device_node *node; + + for_each_node_with_property(node, property_name) { + int ret; + + ret = of_device_ensure_probed(node); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(of_devices_ensure_probed_by_property); + +static int of_stdoutpath_init(void) +{ + struct device_node *np; + + np = of_get_stdoutpath(NULL); + if (!np) + return 0; + + /* + * With deep probe support the device providing the console + * can come quite late in the probe order. Make sure it's + * probed now so that we get output earlier. + */ + return of_device_ensure_probed(np); +} +postconsole_initcall(of_stdoutpath_init); diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 0b513b68d0..684876e260 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -23,6 +23,7 @@ config USB_NOP_XCEIV phy programming such as ISP1x04 etc. source "drivers/phy/freescale/Kconfig" +source "drivers/phy/rockchip/Kconfig" config PHY_STM32_USBPHYC tristate "STM32 USB HS PHY Controller" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 684aaed75a..f06a9df92e 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_GENERIC_PHY) += phy-core.o obj-$(CONFIG_USB_NOP_XCEIV) += usb-nop-xceiv.o obj-y += freescale/ obj-$(CONFIG_PHY_STM32_USBPHYC) += phy-stm32-usbphyc.o +obj-y += rockchip/ diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index ff6e35d160..493876b17f 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -259,6 +259,10 @@ static struct phy *_of_phy_get(struct device_node *np, int index) if (ret) return ERR_PTR(-ENODEV); + ret = of_device_ensure_probed(args.np); + if (ret) + return ERR_PTR(ret); + phy_provider = of_phy_provider_lookup(args.np); if (IS_ERR(phy_provider)) { return ERR_CAST(phy_provider); diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig new file mode 100644 index 0000000000..37a514059e --- /dev/null +++ b/drivers/phy/rockchip/Kconfig @@ -0,0 +1,14 @@ +config PHY_ROCKCHIP_INNO_USB2 + bool "Rockchip INNO USB2PHY Driver" + depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OFDEVICE + depends on COMMON_CLK + help + Support for Rockchip USB2.0 PHY with Innosilicon IP block. + +config PHY_ROCKCHIP_NANENG_COMBO_PHY + bool "Rockchip NANENG COMBO PHY Driver" + depends on ARCH_ROCKCHIP && OFDEVICE + help + Enable this to support the Rockchip PCIe/USB3.0/SATA/QSGMII + combo PHY with NaNeng IP block. + diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile new file mode 100644 index 0000000000..4d75d610ef --- /dev/null +++ b/drivers/phy/rockchip/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o +obj-$(CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY) += phy-rockchip-naneng-combphy.o diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c new file mode 100644 index 0000000000..bb1a5c747e --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -0,0 +1,981 @@ +/* + * Copyright 2017 Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <of.h> +#include <errno.h> +#include <driver.h> +#include <malloc.h> +#include <usb/phy.h> +#include <linux/phy/phy.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <mfd/syscon.h> +#include <regulator.h> + +#define U2PHY_BIT_WRITEABLE_SHIFT 16 +#define CHG_DCD_MAX_RETRIES 6 +#define CHG_PRI_MAX_RETRIES 2 +#define CHG_DCD_POLL_TIME 100 /* millisecond */ +#define CHG_PRIMARY_DET_TIME 40 /* millisecond */ +#define CHG_SECONDARY_DET_TIME 40 /* millisecond */ + +struct rockchip_usb2phy; + +enum power_supply_type { + POWER_SUPPLY_TYPE_UNKNOWN = 0, + POWER_SUPPLY_TYPE_USB, /* Standard Downstream Port */ + POWER_SUPPLY_TYPE_USB_DCP, /* Dedicated Charging Port */ + POWER_SUPPLY_TYPE_USB_CDP, /* Charging Downstream Port */ + POWER_SUPPLY_TYPE_USB_FLOATING, /* DCP without shorting D+/D- */ +}; + +enum rockchip_usb2phy_port_id { + USB2PHY_PORT_OTG, + USB2PHY_PORT_HOST, + USB2PHY_NUM_PORTS, +}; + +struct usb2phy_reg { + u32 offset; + u32 bitend; + u32 bitstart; + u32 disable; + u32 enable; +}; + +/** + * struct rockchip_chg_det_reg: usb charger detect registers + * @cp_det: charging port detected successfully. + * @dcp_det: dedicated charging port detected successfully. + * @dp_det: assert data pin connect successfully. + * @idm_sink_en: open dm sink curren. + * @idp_sink_en: open dp sink current. + * @idp_src_en: open dm source current. + * @rdm_pdwn_en: open dm pull down resistor. + * @vdm_src_en: open dm voltage source. + * @vdp_src_en: open dp voltage source. + * @opmode: utmi operational mode. + */ +struct rockchip_chg_det_reg { + struct usb2phy_reg cp_det; + struct usb2phy_reg dcp_det; + struct usb2phy_reg dp_det; + struct usb2phy_reg idm_sink_en; + struct usb2phy_reg idp_sink_en; + struct usb2phy_reg idp_src_en; + struct usb2phy_reg rdm_pdwn_en; + struct usb2phy_reg vdm_src_en; + struct usb2phy_reg vdp_src_en; + struct usb2phy_reg opmode; +}; + +/** + * struct rockchip_usb2phy_port_cfg: usb-phy port configuration. + * @phy_sus: phy suspend register. + * @bvalid_det_en: vbus valid rise detection enable register. + * @bvalid_det_st: vbus valid rise detection status register. + * @bvalid_det_clr: vbus valid rise detection clear register. + * @ls_det_en: linestate detection enable register. + * @ls_det_st: linestate detection state register. + * @ls_det_clr: linestate detection clear register. + * @iddig_output: iddig output from grf. + * @iddig_en: utmi iddig select between grf and phy, + * 0: from phy; 1: from grf + * @idfall_det_en: id fall detection enable register. + * @idfall_det_st: id fall detection state register. + * @idfall_det_clr: id fall detection clear register. + * @idrise_det_en: id rise detection enable register. + * @idrise_det_st: id rise detection state register. + * @idrise_det_clr: id rise detection clear register. + * @utmi_avalid: utmi vbus avalid status register. + * @utmi_bvalid: utmi vbus bvalid status register. + * @utmi_iddig: otg port id pin status register. + * @utmi_ls: utmi linestate state register. + * @utmi_hstdet: utmi host disconnect register. + * @vbus_det_en: vbus detect function power down register. + */ +struct rockchip_usb2phy_port_cfg { + struct usb2phy_reg phy_sus; + struct usb2phy_reg bvalid_det_en; + struct usb2phy_reg bvalid_det_st; + struct usb2phy_reg bvalid_det_clr; + struct usb2phy_reg ls_det_en; + struct usb2phy_reg ls_det_st; + struct usb2phy_reg ls_det_clr; + struct usb2phy_reg iddig_output; + struct usb2phy_reg iddig_en; + struct usb2phy_reg idfall_det_en; + struct usb2phy_reg idfall_det_st; + struct usb2phy_reg idfall_det_clr; + struct usb2phy_reg idrise_det_en; + struct usb2phy_reg idrise_det_st; + struct usb2phy_reg idrise_det_clr; + struct usb2phy_reg utmi_avalid; + struct usb2phy_reg utmi_bvalid; + struct usb2phy_reg utmi_iddig; + struct usb2phy_reg utmi_ls; + struct usb2phy_reg utmi_hstdet; + struct usb2phy_reg vbus_det_en; +}; + +/** + * struct rockchip_usb2phy_cfg: usb-phy configuration. + * @reg: the address offset of grf for usb-phy config. + * @num_ports: specify how many ports that the phy has. + * @phy_tuning: phy default parameters tunning. + * @clkout_ctl: keep on/turn off output clk of phy. + * @chg_det: charger detection registers. + */ +struct rockchip_usb2phy_cfg { + u32 reg; + u32 num_ports; + int (*phy_tuning)(struct rockchip_usb2phy *); + struct usb2phy_reg clkout_ctl; + const struct rockchip_usb2phy_port_cfg port_cfgs[USB2PHY_NUM_PORTS]; + const struct rockchip_chg_det_reg chg_det; +}; + +struct rockchip_usb2phy_phy { + struct phy *phy; + struct regulator *vbus; + struct rockchip_usb2phy *usb2phy; + const struct rockchip_usb2phy_port_cfg *port_cfg; +}; + +/** + * @dcd_retries: The retry count used to track Data contact + * detection process. + * @primary_retries: The retry count used to do usb bc detection + * primary stage. + * @grf: General Register Files register base. + * @usbgrf_base : USB General Register Files register base. + * @phy_cfg: phy register configuration, assigned by driver data. + */ +struct rockchip_usb2phy { + u8 dcd_retries; + u8 primary_retries; + struct regmap *grf_base; + const struct rockchip_usb2phy_cfg *phy_cfg; + struct rockchip_usb2phy_phy phys[2]; + struct phy_provider *provider; + struct clk *clk480m; + struct clk_hw clk480m_hw; + struct device_d *dev; + struct clk *clk; +}; + +static inline struct regmap *get_reg_base(struct rockchip_usb2phy *rphy) +{ + return rphy->grf_base; +} + +static inline int property_enable(struct regmap *base, + const struct usb2phy_reg *reg, bool en) +{ + u32 val, mask, tmp; + + tmp = en ? reg->enable : reg->disable; + mask = GENMASK(reg->bitend, reg->bitstart); + val = (tmp << reg->bitstart) | (mask << U2PHY_BIT_WRITEABLE_SHIFT); + + return regmap_write(base, reg->offset, val); +} + +static inline bool property_enabled(struct regmap *base, + const struct usb2phy_reg *reg) +{ + u32 tmp, orig; + u32 mask = GENMASK(reg->bitend, reg->bitstart); + + regmap_read(base, reg->offset, &orig); + + tmp = (orig & mask) >> reg->bitstart; + + return tmp == reg->enable; +} + +static int rockchip_usb2phy_init(struct phy *phy) +{ + struct rockchip_usb2phy_phy *p = phy_get_drvdata(phy); + struct rockchip_usb2phy *rphy = p->usb2phy; + struct regmap *base = get_reg_base(rphy); + + p->vbus = regulator_get(&phy->dev, "vbus"); + + property_enable(base, &p->port_cfg->phy_sus, false); + + /* waiting for the utmi_clk to become stable */ + udelay(2000); + + return 0; +} + +static int rockchip_usb2phy_exit(struct phy *phy) +{ + struct rockchip_usb2phy_phy *p = phy_get_drvdata(phy); + struct rockchip_usb2phy *rphy = p->usb2phy; + struct regmap *base = get_reg_base(rphy); + + property_enable(base, &p->port_cfg->phy_sus, true); + + return 0; +} + +static int rockchip_usb2phy_power_on(struct phy *phy) +{ + struct rockchip_usb2phy_phy *p = phy_get_drvdata(phy); + int ret; + + ret = regulator_enable(p->vbus); + if (ret) { + dev_err(&phy->dev, "Failed to enable VBus supply\n"); + return ret; + } + + return 0; +} + +static int rockchip_usb2phy_power_off(struct phy *phy) +{ + struct rockchip_usb2phy_phy *p = phy_get_drvdata(phy); + int ret; + + ret = regulator_disable(p->vbus); + if (ret) { + dev_err(&phy->dev, "Failed to disable VBus supply\n"); + return ret; + } + + return 0; +} + +static struct phy *rockchip_usb2phy_of_xlate(struct device_d *dev, + struct of_phandle_args *args) +{ + struct rockchip_usb2phy *rphy = dev->priv; + struct device_node *phynode = args->np; + struct rockchip_usb2phy_phy *p; + int port; + + for (port = 0; port < 2; port++) { + if (phynode == rphy->phys[port].phy->dev.device_node) { + p = &rphy->phys[port]; + return p->phy; + } + } + + return NULL; +} + +static struct phy_ops rockchip_usb2phy_ops = { + .init = rockchip_usb2phy_init, + .exit = rockchip_usb2phy_exit, + .power_on = rockchip_usb2phy_power_on, + .power_off = rockchip_usb2phy_power_off, +}; + +static int rockchip_usb2phy_clk480m_prepare(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + struct regmap *base = get_reg_base(rphy); + int ret; + + /* turn on 480m clk output if it is off */ + if (!property_enabled(base, &rphy->phy_cfg->clkout_ctl)) { + ret = property_enable(base, &rphy->phy_cfg->clkout_ctl, true); + if (ret) + return ret; + + /* waiting for the clk become stable */ + udelay(1200); + } + + return 0; +} + +static void rockchip_usb2phy_clk480m_unprepare(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + struct regmap *base = get_reg_base(rphy); + + /* turn off 480m clk output */ + property_enable(base, &rphy->phy_cfg->clkout_ctl, false); +} + +static int rockchip_usb2phy_clk480m_prepared(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + struct regmap *base = get_reg_base(rphy); + + return property_enabled(base, &rphy->phy_cfg->clkout_ctl); +} + +static unsigned long +rockchip_usb2phy_clk480m_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return 480000000; +} + +static const struct clk_ops rockchip_usb2phy_clkout_ops = { + .enable = rockchip_usb2phy_clk480m_prepare, + .disable = rockchip_usb2phy_clk480m_unprepare, + .is_enabled = rockchip_usb2phy_clk480m_prepared, + .recalc_rate = rockchip_usb2phy_clk480m_recalc_rate, +}; + +static int rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) +{ + struct device_node *node = rphy->dev->device_node; + struct clk_init_data init = {}; + const char *clk_name; + int ret; + + init.flags = 0; + init.name = "clk_usbphy_480m"; + init.ops = &rockchip_usb2phy_clkout_ops; + + /* optional override of the clockname */ + of_property_read_string(node, "clock-output-names", &init.name); + + if (rphy->clk) { + clk_name = __clk_get_name(rphy->clk); + init.parent_names = &clk_name; + init.num_parents = 1; + } else { + init.parent_names = NULL; + init.num_parents = 0; + } + + rphy->clk480m_hw.init = &init; + + rphy->clk480m = clk_register(rphy->dev, &rphy->clk480m_hw); + if (IS_ERR(rphy->clk480m)) { + ret = PTR_ERR(rphy->clk480m); + goto err_ret; + } + + ret = of_clk_add_provider(node, of_clk_src_simple_get, rphy->clk480m); + if (ret < 0) + goto err_clk_provider; + + return 0; + +err_clk_provider: + clk_unregister(rphy->clk480m); +err_ret: + return ret; +} + +static int rockchip_usb2phy_probe(struct device_d *dev) +{ + const struct rockchip_usb2phy_cfg *phy_cfgs; + struct rockchip_usb2phy *rphy; + u32 reg, index; + int ret, port = 0; + struct device_node *child, *np = dev->device_node; + struct resource *iores; + + rphy = xzalloc(sizeof(*rphy)); + + rphy->dev = dev; + + rphy->grf_base = syscon_regmap_lookup_by_phandle(np, "rockchip,usbgrf"); + if (IS_ERR(rphy->grf_base)) + return PTR_ERR(rphy->grf_base); + + phy_cfgs = device_get_match_data(dev); + if (!phy_cfgs) + return -EINVAL; + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) { + if (of_property_read_u32(np, "reg", ®)) + return -EINVAL; + } else { + reg = iores->start; + } + + /* find out a proper config which can be matched with dt. */ + index = 0; + while (phy_cfgs[index].reg) { + if (phy_cfgs[index].reg == reg) { + rphy->phy_cfg = &phy_cfgs[index]; + break; + } + ++index; + } + + if (!rphy->phy_cfg) { + dev_err(dev, "no phy-config can be matched\n"); + return -EINVAL; + } + + for_each_child_of_node(np, child) { + struct rockchip_usb2phy_phy *p; + struct phy *phy; + + if (!strcmp(child->name, "host-port")) { + port = USB2PHY_PORT_OTG; + } else if (!strcmp(child->name, "otg-port")) { + port = USB2PHY_PORT_HOST; + } else { + dev_warn(dev, "Ignoring unknown subnode %s\n", child->name); + continue; + } + + if (rphy->phys[port].phy) + return -EINVAL; + + phy = phy_create(dev, child, &rockchip_usb2phy_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create phy%d: %d\n", + port, ret); + return ret; + } + + p = xzalloc(sizeof(*p)); + + phy_set_drvdata(phy, p); + p->usb2phy = rphy; + p->port_cfg = &phy_cfgs->port_cfgs[port]; + + rphy->phys[port].phy = phy; + } + + if (rphy->phy_cfg->phy_tuning) + rphy->phy_cfg->phy_tuning(rphy); + + dev->priv = rphy; + + rphy->clk = clk_get(dev, "phyclk"); + rockchip_usb2phy_clk480m_register(rphy); + + rphy->provider = of_phy_provider_register(dev, rockchip_usb2phy_of_xlate); + if (IS_ERR(rphy->provider)) + return PTR_ERR(rphy->provider); + + return 0; +} + +static int rk322x_usb2phy_tuning(struct rockchip_usb2phy *rphy) +{ + struct regmap *base = get_reg_base(rphy); + int ret = 0; + + /* Open pre-emphasize in non-chirp state for PHY0 otg port */ + if (rphy->phy_cfg->reg == 0x760) + ret = regmap_write(base, 0x76c, 0x00070004); + + return ret; +} + +static const struct rockchip_usb2phy_cfg rk1808_phy_cfgs[] = { + { + .reg = 0x100, + .num_ports = 2, + .clkout_ctl = { 0x108, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0100, 8, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x0110, 2, 2, 0, 1 }, + .bvalid_det_st = { 0x0114, 2, 2, 0, 1 }, + .bvalid_det_clr = { 0x0118, 2, 2, 0, 1 }, + .iddig_output = { 0x0100, 10, 10, 0, 1 }, + .iddig_en = { 0x0100, 9, 9, 0, 1 }, + .idfall_det_en = { 0x0110, 5, 5, 0, 1 }, + .idfall_det_st = { 0x0114, 5, 5, 0, 1 }, + .idfall_det_clr = { 0x0118, 5, 5, 0, 1 }, + .idrise_det_en = { 0x0110, 4, 4, 0, 1 }, + .idrise_det_st = { 0x0114, 4, 4, 0, 1 }, + .idrise_det_clr = { 0x0118, 4, 4, 0, 1 }, + .ls_det_en = { 0x0110, 0, 0, 0, 1 }, + .ls_det_st = { 0x0114, 0, 0, 0, 1 }, + .ls_det_clr = { 0x0118, 0, 0, 0, 1 }, + .utmi_avalid = { 0x0120, 10, 10, 0, 1 }, + .utmi_bvalid = { 0x0120, 9, 9, 0, 1 }, + .utmi_iddig = { 0x0120, 6, 6, 0, 1 }, + .utmi_ls = { 0x0120, 5, 4, 0, 1 }, + .vbus_det_en = { 0x001c, 15, 15, 1, 0 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x104, 8, 0, 0, 0x1d1 }, + .ls_det_en = { 0x110, 1, 1, 0, 1 }, + .ls_det_st = { 0x114, 1, 1, 0, 1 }, + .ls_det_clr = { 0x118, 1, 1, 0, 1 }, + .utmi_ls = { 0x120, 17, 16, 0, 1 }, + .utmi_hstdet = { 0x120, 19, 19, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0100, 3, 0, 5, 1 }, + .cp_det = { 0x0120, 24, 24, 0, 1 }, + .dcp_det = { 0x0120, 23, 23, 0, 1 }, + .dp_det = { 0x0120, 25, 25, 0, 1 }, + .idm_sink_en = { 0x0108, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0108, 7, 7, 0, 1 }, + .idp_src_en = { 0x0108, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0108, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0108, 11, 11, 0, 1 }, + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rk312x_phy_cfgs[] = { + { + .reg = 0x17c, + .num_ports = 2, + .clkout_ctl = { 0x0190, 15, 15, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x017c, 8, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x017c, 14, 14, 0, 1 }, + .bvalid_det_st = { 0x017c, 15, 15, 0, 1 }, + .bvalid_det_clr = { 0x017c, 15, 15, 0, 1 }, + .iddig_output = { 0x017c, 10, 10, 0, 1 }, + .iddig_en = { 0x017c, 9, 9, 0, 1 }, + .idfall_det_en = { 0x01a0, 2, 2, 0, 1 }, + .idfall_det_st = { 0x01a0, 3, 3, 0, 1 }, + .idfall_det_clr = { 0x01a0, 3, 3, 0, 1 }, + .idrise_det_en = { 0x01a0, 0, 0, 0, 1 }, + .idrise_det_st = { 0x01a0, 1, 1, 0, 1 }, + .idrise_det_clr = { 0x01a0, 1, 1, 0, 1 }, + .ls_det_en = { 0x017c, 12, 12, 0, 1 }, + .ls_det_st = { 0x017c, 13, 13, 0, 1 }, + .ls_det_clr = { 0x017c, 13, 13, 0, 1 }, + .utmi_bvalid = { 0x014c, 5, 5, 0, 1 }, + .utmi_iddig = { 0x014c, 8, 8, 0, 1 }, + .utmi_ls = { 0x014c, 7, 6, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0194, 8, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0194, 14, 14, 0, 1 }, + .ls_det_st = { 0x0194, 15, 15, 0, 1 }, + .ls_det_clr = { 0x0194, 15, 15, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x017c, 3, 0, 5, 1 }, + .cp_det = { 0x02c0, 6, 6, 0, 1 }, + .dcp_det = { 0x02c0, 5, 5, 0, 1 }, + .dp_det = { 0x02c0, 7, 7, 0, 1 }, + .idm_sink_en = { 0x0184, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0184, 7, 7, 0, 1 }, + .idp_src_en = { 0x0184, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0184, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0184, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0184, 11, 11, 0, 1 }, + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rk322x_phy_cfgs[] = { + { + .reg = 0x760, + .num_ports = 2, + .phy_tuning = rk322x_usb2phy_tuning, + .clkout_ctl = { 0x0768, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0760, 8, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x0680, 3, 3, 0, 1 }, + .bvalid_det_st = { 0x0690, 3, 3, 0, 1 }, + .bvalid_det_clr = { 0x06a0, 3, 3, 0, 1 }, + .iddig_output = { 0x0760, 10, 10, 0, 1 }, + .iddig_en = { 0x0760, 9, 9, 0, 1 }, + .idfall_det_en = { 0x0680, 6, 6, 0, 1 }, + .idfall_det_st = { 0x0690, 6, 6, 0, 1 }, + .idfall_det_clr = { 0x06a0, 6, 6, 0, 1 }, + .idrise_det_en = { 0x0680, 5, 5, 0, 1 }, + .idrise_det_st = { 0x0690, 5, 5, 0, 1 }, + .idrise_det_clr = { 0x06a0, 5, 5, 0, 1 }, + .ls_det_en = { 0x0680, 2, 2, 0, 1 }, + .ls_det_st = { 0x0690, 2, 2, 0, 1 }, + .ls_det_clr = { 0x06a0, 2, 2, 0, 1 }, + .utmi_bvalid = { 0x0480, 4, 4, 0, 1 }, + .utmi_iddig = { 0x0480, 1, 1, 0, 1 }, + .utmi_ls = { 0x0480, 3, 2, 0, 1 }, + .vbus_det_en = { 0x0788, 15, 15, 1, 0 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0764, 8, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0680, 4, 4, 0, 1 }, + .ls_det_st = { 0x0690, 4, 4, 0, 1 }, + .ls_det_clr = { 0x06a0, 4, 4, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0760, 3, 0, 5, 1 }, + .cp_det = { 0x0884, 4, 4, 0, 1 }, + .dcp_det = { 0x0884, 3, 3, 0, 1 }, + .dp_det = { 0x0884, 5, 5, 0, 1 }, + .idm_sink_en = { 0x0768, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0768, 7, 7, 0, 1 }, + .idp_src_en = { 0x0768, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0768, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0768, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0768, 11, 11, 0, 1 }, + }, + }, + { + .reg = 0x800, + .num_ports = 2, + .clkout_ctl = { 0x0808, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x804, 8, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0684, 1, 1, 0, 1 }, + .ls_det_st = { 0x0694, 1, 1, 0, 1 }, + .ls_det_clr = { 0x06a4, 1, 1, 0, 1 } + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x800, 8, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0684, 0, 0, 0, 1 }, + .ls_det_st = { 0x0694, 0, 0, 0, 1 }, + .ls_det_clr = { 0x06a4, 0, 0, 0, 1 } + } + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rk3328_phy_cfgs[] = { + { + .reg = 0x100, + .num_ports = 2, + .clkout_ctl = { 0x108, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0100, 8, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x0110, 2, 2, 0, 1 }, + .bvalid_det_st = { 0x0114, 2, 2, 0, 1 }, + .bvalid_det_clr = { 0x0118, 2, 2, 0, 1 }, + .iddig_output = { 0x0100, 10, 10, 0, 1 }, + .iddig_en = { 0x0100, 9, 9, 0, 1 }, + .idfall_det_en = { 0x0110, 5, 5, 0, 1 }, + .idfall_det_st = { 0x0114, 5, 5, 0, 1 }, + .idfall_det_clr = { 0x0118, 5, 5, 0, 1 }, + .idrise_det_en = { 0x0110, 4, 4, 0, 1 }, + .idrise_det_st = { 0x0114, 4, 4, 0, 1 }, + .idrise_det_clr = { 0x0118, 4, 4, 0, 1 }, + .ls_det_en = { 0x0110, 0, 0, 0, 1 }, + .ls_det_st = { 0x0114, 0, 0, 0, 1 }, + .ls_det_clr = { 0x0118, 0, 0, 0, 1 }, + .utmi_avalid = { 0x0120, 10, 10, 0, 1 }, + .utmi_bvalid = { 0x0120, 9, 9, 0, 1 }, + .utmi_iddig = { 0x0120, 6, 6, 0, 1 }, + .utmi_ls = { 0x0120, 5, 4, 0, 1 }, + .vbus_det_en = { 0x001c, 15, 15, 1, 0 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x104, 8, 0, 0, 0x1d1 }, + .ls_det_en = { 0x110, 1, 1, 0, 1 }, + .ls_det_st = { 0x114, 1, 1, 0, 1 }, + .ls_det_clr = { 0x118, 1, 1, 0, 1 }, + .utmi_ls = { 0x120, 17, 16, 0, 1 }, + .utmi_hstdet = { 0x120, 19, 19, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0100, 3, 0, 5, 1 }, + .cp_det = { 0x0120, 24, 24, 0, 1 }, + .dcp_det = { 0x0120, 23, 23, 0, 1 }, + .dp_det = { 0x0120, 25, 25, 0, 1 }, + .idm_sink_en = { 0x0108, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0108, 7, 7, 0, 1 }, + .idp_src_en = { 0x0108, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0108, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0108, 11, 11, 0, 1 }, + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rk3368_phy_cfgs[] = { + { + .reg = 0x700, + .num_ports = 2, + .clkout_ctl = { 0x0724, 15, 15, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0700, 8, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x0680, 3, 3, 0, 1 }, + .bvalid_det_st = { 0x0690, 3, 3, 0, 1 }, + .bvalid_det_clr = { 0x06a0, 3, 3, 0, 1 }, + .ls_det_en = { 0x0680, 2, 2, 0, 1 }, + .ls_det_st = { 0x0690, 2, 2, 0, 1 }, + .ls_det_clr = { 0x06a0, 2, 2, 0, 1 }, + .utmi_bvalid = { 0x04bc, 23, 23, 0, 1 }, + .utmi_ls = { 0x04bc, 25, 24, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0728, 8, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0680, 4, 4, 0, 1 }, + .ls_det_st = { 0x0690, 4, 4, 0, 1 }, + .ls_det_clr = { 0x06a0, 4, 4, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0700, 3, 0, 5, 1 }, + .cp_det = { 0x04b8, 30, 30, 0, 1 }, + .dcp_det = { 0x04b8, 29, 29, 0, 1 }, + .dp_det = { 0x04b8, 31, 31, 0, 1 }, + .idm_sink_en = { 0x0718, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0718, 7, 7, 0, 1 }, + .idp_src_en = { 0x0718, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0718, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0718, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0718, 11, 11, 0, 1 }, + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = { + { + .reg = 0xe450, + .num_ports = 2, + .clkout_ctl = { 0xe450, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0xe454, 8, 0, 0x052, 0x1d1 }, + .bvalid_det_en = { 0xe3c0, 3, 3, 0, 1 }, + .bvalid_det_st = { 0xe3e0, 3, 3, 0, 1 }, + .bvalid_det_clr = { 0xe3d0, 3, 3, 0, 1 }, + .idfall_det_en = { 0xe3c0, 5, 5, 0, 1 }, + .idfall_det_st = { 0xe3e0, 5, 5, 0, 1 }, + .idfall_det_clr = { 0xe3d0, 5, 5, 0, 1 }, + .idrise_det_en = { 0xe3c0, 4, 4, 0, 1 }, + .idrise_det_st = { 0xe3e0, 4, 4, 0, 1 }, + .idrise_det_clr = { 0xe3d0, 4, 4, 0, 1 }, + .ls_det_en = { 0xe3c0, 2, 2, 0, 1 }, + .ls_det_st = { 0xe3e0, 2, 2, 0, 1 }, + .ls_det_clr = { 0xe3d0, 2, 2, 0, 1 }, + .utmi_avalid = { 0xe2ac, 7, 7, 0, 1 }, + .utmi_bvalid = { 0xe2ac, 12, 12, 0, 1 }, + .utmi_iddig = { 0xe2ac, 8, 8, 0, 1 }, + .utmi_ls = { 0xe2ac, 14, 13, 0, 1 }, + .vbus_det_en = { 0x449c, 15, 15, 1, 0 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0xe458, 1, 0, 0x2, 0x1 }, + .ls_det_en = { 0xe3c0, 6, 6, 0, 1 }, + .ls_det_st = { 0xe3e0, 6, 6, 0, 1 }, + .ls_det_clr = { 0xe3d0, 6, 6, 0, 1 }, + .utmi_ls = { 0xe2ac, 22, 21, 0, 1 }, + .utmi_hstdet = { 0xe2ac, 23, 23, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0xe454, 3, 0, 5, 1 }, + .cp_det = { 0xe2ac, 2, 2, 0, 1 }, + .dcp_det = { 0xe2ac, 1, 1, 0, 1 }, + .dp_det = { 0xe2ac, 0, 0, 0, 1 }, + .idm_sink_en = { 0xe450, 8, 8, 0, 1 }, + .idp_sink_en = { 0xe450, 7, 7, 0, 1 }, + .idp_src_en = { 0xe450, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0xe450, 10, 10, 0, 1 }, + .vdm_src_en = { 0xe450, 12, 12, 0, 1 }, + .vdp_src_en = { 0xe450, 11, 11, 0, 1 }, + }, + }, + { + .reg = 0xe460, + .num_ports = 2, + .clkout_ctl = { 0xe460, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0xe464, 8, 0, 0x052, 0x1d1 }, + .bvalid_det_en = { 0xe3c0, 8, 8, 0, 1 }, + .bvalid_det_st = { 0xe3e0, 8, 8, 0, 1 }, + .bvalid_det_clr = { 0xe3d0, 8, 8, 0, 1 }, + .idfall_det_en = { 0xe3c0, 10, 10, 0, 1 }, + .idfall_det_st = { 0xe3e0, 10, 10, 0, 1 }, + .idfall_det_clr = { 0xe3d0, 10, 10, 0, 1 }, + .idrise_det_en = { 0xe3c0, 9, 9, 0, 1 }, + .idrise_det_st = { 0xe3e0, 9, 9, 0, 1 }, + .idrise_det_clr = { 0xe3d0, 9, 9, 0, 1 }, + .ls_det_en = { 0xe3c0, 7, 7, 0, 1 }, + .ls_det_st = { 0xe3e0, 7, 7, 0, 1 }, + .ls_det_clr = { 0xe3d0, 7, 7, 0, 1 }, + .utmi_avalid = { 0xe2ac, 10, 10, 0, 1 }, + .utmi_bvalid = { 0xe2ac, 16, 16, 0, 1 }, + .utmi_iddig = { 0xe2ac, 11, 11, 0, 1 }, + .utmi_ls = { 0xe2ac, 18, 17, 0, 1 }, + .vbus_det_en = { 0x451c, 15, 15, 1, 0 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0xe468, 1, 0, 0x2, 0x1 }, + .ls_det_en = { 0xe3c0, 11, 11, 0, 1 }, + .ls_det_st = { 0xe3e0, 11, 11, 0, 1 }, + .ls_det_clr = { 0xe3d0, 11, 11, 0, 1 }, + .utmi_ls = { 0xe2ac, 26, 25, 0, 1 }, + .utmi_hstdet = { 0xe2ac, 27, 27, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0xe464, 3, 0, 5, 1 }, + .cp_det = { 0xe2ac, 5, 5, 0, 1 }, + .dcp_det = { 0xe2ac, 4, 4, 0, 1 }, + .dp_det = { 0xe2ac, 3, 3, 0, 1 }, + .idm_sink_en = { 0xe460, 8, 8, 0, 1 }, + .idp_sink_en = { 0xe460, 7, 7, 0, 1 }, + .idp_src_en = { 0xe460, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0xe460, 10, 10, 0, 1 }, + .vdm_src_en = { 0xe460, 12, 12, 0, 1 }, + .vdp_src_en = { 0xe460, 11, 11, 0, 1 }, + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rv1108_phy_cfgs[] = { + { + .reg = 0x100, + .num_ports = 2, + .clkout_ctl = { 0x108, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0ffa0100, 8, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x0680, 3, 3, 0, 1 }, + .bvalid_det_st = { 0x0690, 3, 3, 0, 1 }, + .bvalid_det_clr = { 0x06a0, 3, 3, 0, 1 }, + .ls_det_en = { 0x0680, 2, 2, 0, 1 }, + .ls_det_st = { 0x0690, 2, 2, 0, 1 }, + .ls_det_clr = { 0x06a0, 2, 2, 0, 1 }, + .utmi_bvalid = { 0x0804, 10, 10, 0, 1 }, + .utmi_ls = { 0x0804, 13, 12, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0ffa0104, 8, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0680, 4, 4, 0, 1 }, + .ls_det_st = { 0x0690, 4, 4, 0, 1 }, + .ls_det_clr = { 0x06a0, 4, 4, 0, 1 }, + .utmi_ls = { 0x0804, 9, 8, 0, 1 }, + .utmi_hstdet = { 0x0804, 7, 7, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0ffa0100, 3, 0, 5, 1 }, + .cp_det = { 0x0804, 1, 1, 0, 1 }, + .dcp_det = { 0x0804, 0, 0, 0, 1 }, + .dp_det = { 0x0804, 2, 2, 0, 1 }, + .idm_sink_en = { 0x0ffa0108, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0ffa0108, 7, 7, 0, 1 }, + .idp_src_en = { 0x0ffa0108, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0ffa0108, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0ffa0108, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0ffa0108, 11, 11, 0, 1 }, + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rk3568_phy_cfgs[] = { + { + .reg = 0xfe8a0000, + .num_ports = 2, + .clkout_ctl = { 0x0008, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0000, 8, 0, 0x052, 0x1d1 }, + .bvalid_det_en = { 0x0080, 2, 2, 0, 1 }, + .bvalid_det_st = { 0x0084, 2, 2, 0, 1 }, + .bvalid_det_clr = { 0x0088, 2, 2, 0, 1 }, + .iddig_output = { 0x0000, 10, 10, 0, 1 }, + .iddig_en = { 0x0000, 9, 9, 0, 1 }, + .idfall_det_en = { 0x0080, 5, 5, 0, 1 }, + .idfall_det_st = { 0x0084, 5, 5, 0, 1 }, + .idfall_det_clr = { 0x0088, 5, 5, 0, 1 }, + .idrise_det_en = { 0x0080, 4, 4, 0, 1 }, + .idrise_det_st = { 0x0084, 4, 4, 0, 1 }, + .idrise_det_clr = { 0x0088, 4, 4, 0, 1 }, + .ls_det_en = { 0x0080, 0, 0, 0, 1 }, + .ls_det_st = { 0x0084, 0, 0, 0, 1 }, + .ls_det_clr = { 0x0088, 0, 0, 0, 1 }, + .utmi_avalid = { 0x00c0, 10, 10, 0, 1 }, + .utmi_bvalid = { 0x00c0, 9, 9, 0, 1 }, + .utmi_iddig = { 0x00c0, 6, 6, 0, 1 }, + .utmi_ls = { 0x00c0, 5, 4, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0004, 8, 0, 0x1d2, 0x1d1 }, + .ls_det_en = { 0x0080, 1, 1, 0, 1 }, + .ls_det_st = { 0x0084, 1, 1, 0, 1 }, + .ls_det_clr = { 0x0088, 1, 1, 0, 1 }, + .utmi_ls = { 0x00c0, 17, 16, 0, 1 }, + .utmi_hstdet = { 0x00c0, 19, 19, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0000, 3, 0, 5, 1 }, + .cp_det = { 0x00c0, 24, 24, 0, 1 }, + .dcp_det = { 0x00c0, 23, 23, 0, 1 }, + .dp_det = { 0x00c0, 25, 25, 0, 1 }, + .idm_sink_en = { 0x0008, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0008, 7, 7, 0, 1 }, + .idp_src_en = { 0x0008, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0008, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0008, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0008, 11, 11, 0, 1 }, + }, + }, + { + .reg = 0xfe8b0000, + .num_ports = 2, + .clkout_ctl = { 0x0008, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0000, 8, 0, 0x1d2, 0x1d1 }, + .ls_det_en = { 0x0080, 0, 0, 0, 1 }, + .ls_det_st = { 0x0084, 0, 0, 0, 1 }, + .ls_det_clr = { 0x0088, 0, 0, 0, 1 }, + .utmi_ls = { 0x00c0, 5, 4, 0, 1 }, + .utmi_hstdet = { 0x00c0, 7, 7, 0, 1 } + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0004, 8, 0, 0x1d2, 0x1d1 }, + .ls_det_en = { 0x0080, 1, 1, 0, 1 }, + .ls_det_st = { 0x0084, 1, 1, 0, 1 }, + .ls_det_clr = { 0x0088, 1, 1, 0, 1 }, + .utmi_ls = { 0x00c0, 17, 16, 0, 1 }, + .utmi_hstdet = { 0x00c0, 19, 19, 0, 1 } + } + }, + }, + { /* sentinel */ } +}; +static const struct of_device_id rockchip_usb2phy_dt_match[] = { + { .compatible = "rockchip,rk1808-usb2phy", .data = &rk1808_phy_cfgs }, + { .compatible = "rockchip,rk3128-usb2phy", .data = &rk312x_phy_cfgs }, + { .compatible = "rockchip,rk322x-usb2phy", .data = &rk322x_phy_cfgs }, + { .compatible = "rockchip,rk3308-usb2phy", .data = &rk3328_phy_cfgs }, + { .compatible = "rockchip,rk3328-usb2phy", .data = &rk3328_phy_cfgs }, + { .compatible = "rockchip,rk3368-usb2phy", .data = &rk3368_phy_cfgs }, + { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs }, + { .compatible = "rockchip,rk3568-usb2phy", .data = &rk3568_phy_cfgs }, + { .compatible = "rockchip,rv1108-usb2phy", .data = &rv1108_phy_cfgs }, + { } +}; + +static struct driver_d rockchip_usb2phy_driver = { + .probe = rockchip_usb2phy_probe, + .name = "rockchip-usb2phy", + .of_compatible = rockchip_usb2phy_dt_match, +}; +coredevice_platform_driver(rockchip_usb2phy_driver); diff --git a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c new file mode 100644 index 0000000000..af4340f90d --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip PIPE USB3.0 PCIE SATA combphy driver + * + * Copyright (C) 2020 Rockchip Electronics Co., Ltd. + */ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <of.h> +#include <errno.h> +#include <driver.h> +#include <malloc.h> +#include <usb/phy.h> +#include <linux/phy/phy.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/reset.h> +#include <mfd/syscon.h> +#include <linux/iopoll.h> +#include <dt-bindings/phy/phy.h> + +#define BIT_WRITEABLE_SHIFT 16 + +struct rockchip_combphy_priv; + +struct combphy_reg { + u16 offset; + u16 bitend; + u16 bitstart; + u16 disable; + u16 enable; +}; + +struct rockchip_combphy_grfcfg { + struct combphy_reg pcie_mode_set; + struct combphy_reg usb_mode_set; + struct combphy_reg sgmii_mode_set; + struct combphy_reg qsgmii_mode_set; + struct combphy_reg pipe_rxterm_set; + struct combphy_reg pipe_txelec_set; + struct combphy_reg pipe_txcomp_set; + struct combphy_reg pipe_clk_25m; + struct combphy_reg pipe_clk_100m; + struct combphy_reg pipe_phymode_sel; + struct combphy_reg pipe_rate_sel; + struct combphy_reg pipe_rxterm_sel; + struct combphy_reg pipe_txelec_sel; + struct combphy_reg pipe_txcomp_sel; + struct combphy_reg pipe_clk_ext; + struct combphy_reg pipe_sel_usb; + struct combphy_reg pipe_sel_qsgmii; + struct combphy_reg pipe_phy_status; + struct combphy_reg con0_for_pcie; + struct combphy_reg con1_for_pcie; + struct combphy_reg con2_for_pcie; + struct combphy_reg con3_for_pcie; + struct combphy_reg con0_for_sata; + struct combphy_reg con1_for_sata; + struct combphy_reg con2_for_sata; + struct combphy_reg con3_for_sata; + struct combphy_reg pipe_con0_for_sata; + struct combphy_reg pipe_sgmii_mac_sel; + struct combphy_reg pipe_xpcs_phy_ready; + struct combphy_reg u3otg0_port_en; + struct combphy_reg u3otg1_port_en; +}; + +struct rockchip_combphy_cfg { + const int num_clks; + const struct clk_bulk_data *clks; + const struct rockchip_combphy_grfcfg *grfcfg; + int (*combphy_cfg)(struct rockchip_combphy_priv *priv); +}; + +struct rockchip_combphy_priv { + u8 mode; + void __iomem *mmio; + int num_clks; + struct clk_bulk_data *clks; + struct device_d *dev; + struct regmap *pipe_grf; + struct regmap *phy_grf; + struct phy *phy; + struct reset_control *apb_rst; + struct reset_control *phy_rst; + const struct rockchip_combphy_cfg *cfg; +}; + +static inline bool param_read(struct regmap *base, + const struct combphy_reg *reg, u32 val) +{ + int ret; + u32 mask, orig, tmp; + + ret = regmap_read(base, reg->offset, &orig); + if (ret) + return false; + + mask = GENMASK(reg->bitend, reg->bitstart); + tmp = (orig & mask) >> reg->bitstart; + + return tmp == val; +} + +static int param_write(struct regmap *base, + const struct combphy_reg *reg, bool en) +{ + u32 val, mask, tmp; + + tmp = en ? reg->enable : reg->disable; + mask = GENMASK(reg->bitend, reg->bitstart); + val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT); + + return regmap_write(base, reg->offset, val); +} + +static u32 rockchip_combphy_is_ready(struct rockchip_combphy_priv *priv) +{ + const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg; + u32 mask, val; + + mask = GENMASK(cfg->pipe_phy_status.bitend, + cfg->pipe_phy_status.bitstart); + + regmap_read(priv->phy_grf, cfg->pipe_phy_status.offset, &val); + val = (val & mask) >> cfg->pipe_phy_status.bitstart; + + return val; +} + +static int rockchip_combphy_pcie_init(struct rockchip_combphy_priv *priv) +{ + int ret = 0; + + if (priv->cfg->combphy_cfg) { + ret = priv->cfg->combphy_cfg(priv); + if (ret) { + dev_err(priv->dev, "failed to init phy for pcie\n"); + return ret; + } + } + + return ret; +} + +static int rockchip_combphy_usb3_init(struct rockchip_combphy_priv *priv) +{ + int ret = 0; + + if (priv->cfg->combphy_cfg) { + ret = priv->cfg->combphy_cfg(priv); + if (ret) { + dev_err(priv->dev, "failed to init phy for usb3\n"); + return ret; + } + } + + return ret; +} + +static int rockchip_combphy_sata_init(struct rockchip_combphy_priv *priv) +{ + int ret = 0; + + if (priv->cfg->combphy_cfg) { + ret = priv->cfg->combphy_cfg(priv); + if (ret) { + dev_err(priv->dev, "failed to init phy for sata\n"); + return ret; + } + } + + return ret; +} + +static int rockchip_combphy_sgmii_init(struct rockchip_combphy_priv *priv) +{ + int ret = 0; + + if (priv->cfg->combphy_cfg) { + ret = priv->cfg->combphy_cfg(priv); + if (ret) { + dev_err(priv->dev, "failed to init phy for sgmii\n"); + return ret; + } + } + + return ret; +} + +static int rockchip_combphy_set_mode(struct rockchip_combphy_priv *priv) +{ + switch (priv->mode) { + case PHY_TYPE_PCIE: + rockchip_combphy_pcie_init(priv); + break; + case PHY_TYPE_USB3: + rockchip_combphy_usb3_init(priv); + break; + case PHY_TYPE_SATA: + rockchip_combphy_sata_init(priv); + break; + case PHY_TYPE_SGMII: + case PHY_TYPE_QSGMII: + return rockchip_combphy_sgmii_init(priv); + default: + dev_err(priv->dev, "incompatible PHY type\n"); + return -EINVAL; + } + + return 0; +} + +static int rockchip_combphy_init(struct phy *phy) +{ + struct rockchip_combphy_priv *priv = phy_get_drvdata(phy); + const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg; + u32 val; + int ret; + + ret = clk_bulk_enable(priv->num_clks, priv->clks); + if (ret) { + dev_err(priv->dev, "failed to enable clks\n"); + return ret; + } + + ret = rockchip_combphy_set_mode(priv); + if (ret) + goto err_clk; + + ret = reset_control_deassert(priv->phy_rst); + if (ret) + goto err_clk; + + if (priv->mode == PHY_TYPE_USB3) { + ret = readx_poll_timeout(rockchip_combphy_is_ready, + priv, val, + val == cfg->pipe_phy_status.enable, + 1000); + if (ret) + dev_warn(priv->dev, "wait phy status ready timeout\n"); + } + + return 0; + +err_clk: + clk_bulk_disable(priv->num_clks, priv->clks); + + return ret; +} + +static int rockchip_combphy_exit(struct phy *phy) +{ + struct rockchip_combphy_priv *priv = phy_get_drvdata(phy); + + clk_bulk_disable(priv->num_clks, priv->clks); + reset_control_assert(priv->phy_rst); + + return 0; +} + +static const struct phy_ops rochchip_combphy_ops = { + .init = rockchip_combphy_init, + .exit = rockchip_combphy_exit, +}; + +static struct phy *rockchip_combphy_xlate(struct device_d *dev, + struct of_phandle_args *args) +{ + struct rockchip_combphy_priv *priv = dev->priv; + + if (args->args_count != 1) { + dev_err(dev, "invalid number of arguments\n"); + return ERR_PTR(-EINVAL); + } + + if (priv->mode != PHY_NONE && priv->mode != args->args[0]) + dev_warn(dev, "phy type select %d overwriting type %d\n", + args->args[0], priv->mode); + + priv->mode = args->args[0]; + + return priv->phy; +} + +static int rockchip_combphy_parse_dt(struct device_d *dev, + struct rockchip_combphy_priv *priv) +{ + struct device_node *np = dev->device_node; + const struct rockchip_combphy_cfg *phy_cfg = priv->cfg; + int ret, mac_id; + + ret = clk_bulk_get(dev, priv->num_clks, priv->clks); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (ret) + priv->num_clks = 0; + + priv->pipe_grf = syscon_regmap_lookup_by_phandle(np, + "rockchip,pipe-grf"); + if (IS_ERR(priv->pipe_grf)) { + dev_err(dev, "failed to find peri_ctrl pipe-grf regmap\n"); + return PTR_ERR(priv->pipe_grf); + } + + priv->phy_grf = syscon_regmap_lookup_by_phandle(np, + "rockchip,pipe-phy-grf"); + if (IS_ERR(priv->phy_grf)) { + dev_err(dev, "failed to find peri_ctrl pipe-phy-grf regmap\n"); + return PTR_ERR(priv->phy_grf); + } + + if (!of_property_read_u32(np, "rockchip,sgmii-mac-sel", &mac_id) && + (mac_id > 0)) + param_write(priv->pipe_grf, &phy_cfg->grfcfg->pipe_sgmii_mac_sel, + true); + + priv->apb_rst = reset_control_get(dev, "combphy-apb"); + if (IS_ERR(priv->apb_rst)) { + ret = PTR_ERR(priv->apb_rst); + + if (ret != -EPROBE_DEFER) + dev_warn(dev, "failed to get apb reset\n"); + + return ret; + } + + priv->phy_rst = reset_control_get(dev, "combphy"); + if (IS_ERR(priv->phy_rst)) { + ret = PTR_ERR(priv->phy_rst); + + if (ret != -EPROBE_DEFER) + dev_warn(dev, "failed to get phy reset\n"); + + return ret; + } + + return reset_control_assert(priv->phy_rst); +} + +static int rockchip_combphy_probe(struct device_d *dev) +{ + struct phy_provider *phy_provider; + struct rockchip_combphy_priv *priv; + const struct rockchip_combphy_cfg *phy_cfg; + struct resource *res; + int ret; + + phy_cfg = device_get_match_data(dev); + if (!phy_cfg) { + dev_err(dev, "No OF match data provided\n"); + return -EINVAL; + } + + priv = xzalloc(sizeof(*priv)); + if (!priv) + return -ENOMEM; + + res = dev_request_mem_resource(dev, 0); + if (IS_ERR(res)) { + ret = PTR_ERR(res); + return ret; + } + + priv->mmio = IOMEM(res->start); + + priv->num_clks = phy_cfg->num_clks; + + priv->clks = memdup(phy_cfg->clks, + phy_cfg->num_clks * sizeof(struct clk_bulk_data)); + if (!priv->clks) + return -ENOMEM; + + priv->dev = dev; + priv->mode = PHY_NONE; + priv->cfg = phy_cfg; + + ret = rockchip_combphy_parse_dt(dev, priv); + if (ret) + return ret; + + priv->phy = phy_create(dev, NULL, &rochchip_combphy_ops); + if (IS_ERR(priv->phy)) { + dev_err(dev, "failed to create combphy\n"); + return PTR_ERR(priv->phy); + } + + dev->priv = priv; + phy_set_drvdata(priv->phy, priv); + + phy_provider = of_phy_provider_register(dev, rockchip_combphy_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static int rk3568_combphy_cfg(struct rockchip_combphy_priv *priv) +{ + struct device_node *np = priv->dev->device_node; + const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg; + struct clk *refclk = NULL; + unsigned long rate; + u32 val; + + /* Configure PHY reference clock frequency */ + refclk = priv->clks[0].clk; + if (!refclk) { + dev_err(priv->dev, "No refclk found\n"); + return -EINVAL; + } + + switch (priv->mode) { + case PHY_TYPE_PCIE: + /* Set SSC downward spread spectrum */ + val = readl(priv->mmio + (0x1f << 2)); + val &= ~GENMASK(5, 4); + val |= 0x01 << 4; + writel(val, priv->mmio + 0x7c); + + param_write(priv->phy_grf, &cfg->con0_for_pcie, true); + param_write(priv->phy_grf, &cfg->con1_for_pcie, true); + param_write(priv->phy_grf, &cfg->con2_for_pcie, true); + param_write(priv->phy_grf, &cfg->con3_for_pcie, true); + break; + case PHY_TYPE_USB3: + /* Set SSC downward spread spectrum */ + val = readl(priv->mmio + (0x1f << 2)); + val &= ~GENMASK(5, 4); + val |= 0x01 << 4; + writel(val, priv->mmio + 0x7c); + + /* Enable adaptive CTLE for USB3.0 Rx */ + val = readl(priv->mmio + (0x0e << 2)); + val &= ~GENMASK(0, 0); + val |= 0x01; + writel(val, priv->mmio + (0x0e << 2)); + + param_write(priv->phy_grf, &cfg->pipe_sel_usb, true); + param_write(priv->phy_grf, &cfg->pipe_txcomp_sel, false); + param_write(priv->phy_grf, &cfg->pipe_txelec_sel, false); + param_write(priv->phy_grf, &cfg->usb_mode_set, true); + break; + case PHY_TYPE_SATA: + writel(0x41, priv->mmio + 0x38); + writel(0x8F, priv->mmio + 0x18); + param_write(priv->phy_grf, &cfg->con0_for_sata, true); + param_write(priv->phy_grf, &cfg->con1_for_sata, true); + param_write(priv->phy_grf, &cfg->con2_for_sata, true); + param_write(priv->phy_grf, &cfg->con3_for_sata, true); + param_write(priv->pipe_grf, &cfg->pipe_con0_for_sata, true); + break; + case PHY_TYPE_SGMII: + param_write(priv->pipe_grf, &cfg->pipe_xpcs_phy_ready, true); + param_write(priv->phy_grf, &cfg->pipe_phymode_sel, true); + param_write(priv->phy_grf, &cfg->pipe_sel_qsgmii, true); + param_write(priv->phy_grf, &cfg->sgmii_mode_set, true); + break; + case PHY_TYPE_QSGMII: + param_write(priv->pipe_grf, &cfg->pipe_xpcs_phy_ready, true); + param_write(priv->phy_grf, &cfg->pipe_phymode_sel, true); + param_write(priv->phy_grf, &cfg->pipe_rate_sel, true); + param_write(priv->phy_grf, &cfg->pipe_sel_qsgmii, true); + param_write(priv->phy_grf, &cfg->qsgmii_mode_set, true); + break; + default: + dev_err(priv->dev, "incompatible PHY type\n"); + return -EINVAL; + } + + rate = clk_get_rate(refclk); + + switch (rate) { + case 24000000: + if (priv->mode == PHY_TYPE_USB3 || priv->mode == PHY_TYPE_SATA) { + /* Set ssc_cnt[9:0]=0101111101 & 31.5KHz */ + val = readl(priv->mmio + (0x0e << 2)); + val &= ~GENMASK(7, 6); + val |= 0x01 << 6; + writel(val, priv->mmio + (0x0e << 2)); + + val = readl(priv->mmio + (0x0f << 2)); + val &= ~GENMASK(7, 0); + val |= 0x5f; + writel(val, priv->mmio + (0x0f << 2)); + } + break; + case 25000000: + param_write(priv->phy_grf, &cfg->pipe_clk_25m, true); + break; + case 100000000: + param_write(priv->phy_grf, &cfg->pipe_clk_100m, true); + if (priv->mode == PHY_TYPE_PCIE) { + /* PLL KVCO tuning fine */ + val = readl(priv->mmio + (0x20 << 2)); + val &= ~(0x7 << 2); + val |= 0x2 << 2; + writel(val, priv->mmio + (0x20 << 2)); + + /* Enable controlling random jitter, aka RMJ */ + writel(0x4, priv->mmio + (0xb << 2)); + + val = readl(priv->mmio + (0x5 << 2)); + val &= ~(0x3 << 6); + val |= 0x1 << 6; + writel(val, priv->mmio + (0x5 << 2)); + + writel(0x32, priv->mmio + (0x11 << 2)); + writel(0xf0, priv->mmio + (0xa << 2)); + } else if (priv->mode == PHY_TYPE_SATA) { + /* downward spread spectrum +500ppm */ + val = readl(priv->mmio + (0x1f << 2)); + val &= ~GENMASK(7, 4); + val |= 0x50; + writel(val, priv->mmio + (0x1f << 2)); + } + break; + default: + dev_err(priv->dev, "Unsupported rate: %lu\n", rate); + return -EINVAL; + } + + if (of_property_read_bool(np, "rockchip,ext-refclk")) { + param_write(priv->phy_grf, &cfg->pipe_clk_ext, true); + if (priv->mode == PHY_TYPE_PCIE && rate == 100000000) { + val = readl(priv->mmio + (0xc << 2)); + val |= 0x3 << 4 | 0x1 << 7; + writel(val, priv->mmio + (0xc << 2)); + + val = readl(priv->mmio + (0xd << 2)); + val |= 0x1; + writel(val, priv->mmio + (0xd << 2)); + } + } + + if (of_property_read_bool(np, "rockchip,enable-ssc")) { + val = readl(priv->mmio + (0x7 << 2)); + val |= BIT(4); + writel(val, priv->mmio + (0x7 << 2)); + } + + return 0; +} + +static const struct rockchip_combphy_grfcfg rk3568_combphy_grfcfgs = { + /* pipe-phy-grf */ + .pcie_mode_set = { 0x0000, 5, 0, 0x00, 0x11 }, + .usb_mode_set = { 0x0000, 5, 0, 0x00, 0x04 }, + .sgmii_mode_set = { 0x0000, 5, 0, 0x00, 0x01 }, + .qsgmii_mode_set = { 0x0000, 5, 0, 0x00, 0x21 }, + .pipe_rxterm_set = { 0x0000, 12, 12, 0x00, 0x01 }, + .pipe_txelec_set = { 0x0004, 1, 1, 0x00, 0x01 }, + .pipe_txcomp_set = { 0x0004, 4, 4, 0x00, 0x01 }, + .pipe_clk_25m = { 0x0004, 14, 13, 0x00, 0x01 }, + .pipe_clk_100m = { 0x0004, 14, 13, 0x00, 0x02 }, + .pipe_phymode_sel = { 0x0008, 1, 1, 0x00, 0x01 }, + .pipe_rate_sel = { 0x0008, 2, 2, 0x00, 0x01 }, + .pipe_rxterm_sel = { 0x0008, 8, 8, 0x00, 0x01 }, + .pipe_txelec_sel = { 0x0008, 12, 12, 0x00, 0x01 }, + .pipe_txcomp_sel = { 0x0008, 15, 15, 0x00, 0x01 }, + .pipe_clk_ext = { 0x000c, 9, 8, 0x02, 0x01 }, + .pipe_sel_usb = { 0x000c, 14, 13, 0x00, 0x01 }, + .pipe_sel_qsgmii = { 0x000c, 15, 13, 0x00, 0x07 }, + .pipe_phy_status = { 0x0034, 6, 6, 0x01, 0x00 }, + .con0_for_pcie = { 0x0000, 15, 0, 0x00, 0x1000 }, + .con1_for_pcie = { 0x0004, 15, 0, 0x00, 0x0000 }, + .con2_for_pcie = { 0x0008, 15, 0, 0x00, 0x0101 }, + .con3_for_pcie = { 0x000c, 15, 0, 0x00, 0x0200 }, + .con0_for_sata = { 0x0000, 15, 0, 0x00, 0x0119 }, + .con1_for_sata = { 0x0004, 15, 0, 0x00, 0x0040 }, + .con2_for_sata = { 0x0008, 15, 0, 0x00, 0x80c3 }, + .con3_for_sata = { 0x000c, 15, 0, 0x00, 0x4407 }, + /* pipe-grf */ + .pipe_con0_for_sata = { 0x0000, 15, 0, 0x00, 0x2220 }, + .pipe_sgmii_mac_sel = { 0x0040, 1, 1, 0x00, 0x01 }, + .pipe_xpcs_phy_ready = { 0x0040, 2, 2, 0x00, 0x01 }, + .u3otg0_port_en = { 0x0104, 15, 0, 0x0181, 0x1100 }, + .u3otg1_port_en = { 0x0144, 15, 0, 0x0181, 0x1100 }, +}; + +static const struct clk_bulk_data rk3568_clks[] = { + { .id = "refclk" }, + { .id = "apbclk" }, + { .id = "pipe_clk" }, +}; + +static const struct rockchip_combphy_cfg rk3568_combphy_cfgs = { + .num_clks = ARRAY_SIZE(rk3568_clks), + .clks = rk3568_clks, + .grfcfg = &rk3568_combphy_grfcfgs, + .combphy_cfg = rk3568_combphy_cfg, +}; + +static const struct of_device_id rockchip_combphy_of_match[] = { + { + .compatible = "rockchip,rk3568-naneng-combphy", + .data = &rk3568_combphy_cfgs, + }, + { }, +}; + +static struct driver_d rockchip_combphy_driver = { + .probe = rockchip_combphy_probe, + .name = "naneng-combphy", + .of_compatible = rockchip_combphy_of_match, +}; +coredevice_platform_driver(rockchip_combphy_driver); diff --git a/drivers/pinctrl/pinctrl-rockchip.c b/drivers/pinctrl/pinctrl-rockchip.c index be44067c8f..9b832c97d6 100644 --- a/drivers/pinctrl/pinctrl-rockchip.c +++ b/drivers/pinctrl/pinctrl-rockchip.c @@ -332,63 +332,52 @@ static struct gpio_ops rockchip_gpio_ops = { .get_direction = rockchip_gpiov2_get_direction, }; -static int rockchip_gpiolib_register(struct device_d *dev, - struct rockchip_pinctrl *info) +static int rockchip_gpio_probe(struct device_d *dev) { + struct rockchip_pinctrl *info = dev->parent->priv; struct rockchip_pin_ctrl *ctrl = info->ctrl; - struct rockchip_pin_bank *bank = ctrl->pin_banks; + struct rockchip_pin_bank *bank; + struct gpio_chip *gpio; void __iomem *reg_base; - int ret; - int i; + int ret, bankno; - for (i = 0; i < ctrl->nr_banks; ++i, ++bank) { - struct gpio_chip *gpio = &bank->bgpio_chip.gc; + bankno = of_alias_get_id(dev->device_node, "gpio"); + bank = &ctrl->pin_banks[bankno]; + gpio = &bank->bgpio_chip.gc; - if (!bank->valid) { - dev_warn(dev, "bank %s is not valid\n", bank->name); - continue; - } + if (!bank->valid) + dev_warn(dev, "bank %s is not valid\n", bank->name); - reg_base = bank->reg_base; - - if (ctrl->type == RK3568) { - gpio->ngpio = 32; - gpio->dev = dev; - gpio->ops = &rockchip_gpio_ops; - gpio->base = of_alias_get_id(bank->of_node, "gpio"); - if (gpio->base < 0) - return -EINVAL; - gpio->base *= 32; - } else { - ret = bgpio_init(&bank->bgpio_chip, dev, 4, - reg_base + RK_GPIO_EXT_PORT, - reg_base + RK_GPIO_SWPORT_DR, NULL, - reg_base + RK_GPIO_SWPORT_DDR, NULL, 0); - if (ret) - goto fail; - } + reg_base = bank->reg_base; - bank->bgpio_chip.gc.dev = of_find_device_by_node(bank->of_node); + if (ctrl->type == RK3568) { + gpio->ngpio = 32; + gpio->dev = dev; + gpio->ops = &rockchip_gpio_ops; + gpio->base = bankno; + if (gpio->base < 0) + return -EINVAL; + gpio->base *= 32; + } else { + ret = bgpio_init(&bank->bgpio_chip, dev, 4, + reg_base + RK_GPIO_EXT_PORT, + reg_base + RK_GPIO_SWPORT_DR, NULL, + reg_base + RK_GPIO_SWPORT_DDR, NULL, 0); + if (ret) + return ret; + } - bank->bgpio_chip.gc.ngpio = bank->nr_pins; - ret = gpiochip_add(&bank->bgpio_chip.gc); - if (ret) { - dev_err(dev, "failed to register gpio_chip %s, error code: %d\n", - bank->name, ret); - goto fail; - } + bank->bgpio_chip.gc.dev = dev; + bank->bgpio_chip.gc.ngpio = bank->nr_pins; + ret = gpiochip_add(&bank->bgpio_chip.gc); + if (ret) { + dev_err(dev, "failed to register gpio_chip %s, error code: %d\n", + bank->name, ret); + return ret; } return 0; -fail: - for (--i, --bank; i >= 0; --i, --bank) { - if (!bank->valid) - continue; - - gpiochip_remove(&bank->bgpio_chip.gc); - } - return ret; } static struct rockchip_pinctrl *to_rockchip_pinctrl(struct pinctrl_device *pdev) @@ -1010,8 +999,6 @@ static int rockchip_pinctrl_probe(struct device_d *dev) struct rockchip_pin_ctrl *ctrl; int ret; - of_platform_populate(dev->device_node, NULL, NULL); - info = xzalloc(sizeof(struct rockchip_pinctrl)); ctrl = rockchip_pinctrl_get_soc_data(info, dev); @@ -1038,9 +1025,9 @@ static int rockchip_pinctrl_probe(struct device_d *dev) info->pctl_dev.dev = dev; info->pctl_dev.ops = &rockchip_pinctrl_ops; - ret = rockchip_gpiolib_register(dev, info); - if (ret) - return ret; + dev->priv = info; + + of_platform_populate(dev->device_node, NULL, dev); if (!IS_ENABLED(CONFIG_PINCTRL)) return 0; @@ -1282,3 +1269,20 @@ static struct driver_d rockchip_pinctrl_driver = { }; core_platform_driver(rockchip_pinctrl_driver); + +static struct of_device_id rockchip_gpio_dt_match[] = { + { + .compatible = "rockchip,gpio-bank", + .data = &rk2928_pin_ctrl, + }, { + /* sentinel */ + } +}; + +static struct driver_d rockchip_gpio_driver = { + .name = "rockchip-gpio", + .probe = rockchip_gpio_probe, + .of_compatible = DRV_OF_COMPAT(rockchip_gpio_dt_match), +}; + +core_platform_driver(rockchip_gpio_driver); diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c index c774660232..cf2f724d18 100644 --- a/drivers/pinctrl/pinctrl-single.c +++ b/drivers/pinctrl/pinctrl-single.c @@ -128,7 +128,7 @@ static struct pinctrl_ops pcs_ops = { .set_state = pcs_set_state, }; -static int pcs_probe(struct device_d *dev) +int pinctrl_single_probe(struct device_d *dev) { struct resource *iores; struct pinctrl_single *pcs; @@ -215,7 +215,7 @@ static __maybe_unused struct of_device_id pcs_dt_ids[] = { static struct driver_d pcs_driver = { .name = "pinctrl-single", - .probe = pcs_probe, + .probe = pinctrl_single_probe, .of_compatible = DRV_OF_COMPAT(pcs_dt_ids), }; diff --git a/drivers/pinctrl/pinctrl-stm32.c b/drivers/pinctrl/pinctrl-stm32.c index 09b62309f6..97a643a4da 100644 --- a/drivers/pinctrl/pinctrl-stm32.c +++ b/drivers/pinctrl/pinctrl-stm32.c @@ -412,11 +412,14 @@ static int stm32_pinctrl_probe(struct device_d *dev) if (!of_property_read_bool(child, "gpio-controller")) continue; - ret = stm32_gpiochip_add(gpio_bank++, child, dev); + ret = stm32_gpiochip_add(gpio_bank, child, dev); if (ret) { dev_err(dev, "couldn't add gpiochip %s, ret = %d\n", child->name, ret); return ret; } + + of_platform_device_dummy_drv(gpio_bank->chip.dev); + gpio_bank++; } dev_dbg(dev, "pinctrl/gpio driver registered\n"); diff --git a/drivers/pinctrl/pinctrl.c b/drivers/pinctrl/pinctrl.c index b36521ef83..7e88391ff3 100644 --- a/drivers/pinctrl/pinctrl.c +++ b/drivers/pinctrl/pinctrl.c @@ -76,7 +76,7 @@ static struct pinctrl_device *find_pinctrl(struct device_node *node) return NULL; } -static int pinctrl_config_one(struct device_node *np) +static int pinctrl_config_one(struct device_node *for_node, struct device_node *np) { struct pinctrl_device *pdev; struct device_node *pinctrl_node = np; @@ -85,12 +85,18 @@ static int pinctrl_config_one(struct device_node *np) pinctrl_node = pinctrl_node->parent; if (!pinctrl_node) return -ENODEV; - pdev = find_pinctrl(pinctrl_node); - if (pdev) + if (of_get_property(pinctrl_node, "compatible", NULL)) break; } - return pdev->ops->set_state(pdev, np); + if (pinctrl_node != for_node) + of_device_ensure_probed(pinctrl_node); + + pdev = find_pinctrl(pinctrl_node); + if (pdev) + return pdev->ops->set_state(pdev, np); + else + return -ENODEV; } int of_pinctrl_select_state(struct device_node *np, const char *name) @@ -148,7 +154,7 @@ int of_pinctrl_select_state(struct device_node *np, const char *name) } /* Parse the node */ - ret = pinctrl_config_one(np_config); + ret = pinctrl_config_one(np, np_config); if (ret < 0) goto err; } diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index dec1482ccd..e4151d8bc6 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -13,6 +13,16 @@ config SYSCON_REBOOT_MODE Say y here will enable reboot mode driver. This will get reboot mode arguments and store it in SYSCON mapped register, then the bootloader can read it to take different + +config NVMEM_REBOOT_MODE + bool "Generic NVMEM reboot mode driver" + depends on OFDEVICE + depends on NVMEM + select REBOOT_MODE + help + Say y here will enable reboot mode driver. This will + get reboot mode arguments and store it in a NVMEM cell, + then the bootloader can read it and take different action according to the mode. config POWER_RESET_SYSCON diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 33d29d2d95..10d6f2a41e 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o +obj-$(CONFIG_NVMEM_REBOOT_MODE) += nvmem-reboot-mode.o obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c new file mode 100644 index 0000000000..b82b37d642 --- /dev/null +++ b/drivers/power/reset/nvmem-reboot-mode.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) Vaisala Oyj. All rights reserved. + */ + +#include <common.h> +#include <init.h> +#include <of.h> +#include <linux/nvmem-consumer.h> +#include <linux/reboot-mode.h> + +struct nvmem_reboot_mode { + struct reboot_mode_driver reboot; + struct nvmem_cell *cell; +}; + +static int nvmem_reboot_mode_write(struct reboot_mode_driver *reboot, + const u32 *_magic) +{ + struct nvmem_reboot_mode *nvmem_rbm; + u32 magic = *_magic; + int ret; + + nvmem_rbm = container_of(reboot, struct nvmem_reboot_mode, reboot); + + ret = nvmem_cell_write(nvmem_rbm->cell, &magic, sizeof(magic)); + if (ret < 0) + dev_err(reboot->dev, "update reboot mode bits failed: %pe\n", ERR_PTR(ret)); + else if (ret != 4) + ret = -EIO; + else + ret = 0; + + return ret; +} + +static int nvmem_reboot_mode_probe(struct device_d *dev) +{ + struct nvmem_reboot_mode *nvmem_rbm; + struct nvmem_cell *cell; + void *magicbuf; + size_t len; + int ret; + + cell = nvmem_cell_get(dev, "reboot-mode"); + if (IS_ERR(cell)) { + ret = PTR_ERR(cell); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get the nvmem cell reboot-mode: %pe\n", cell); + return ret; + } + + nvmem_rbm = xzalloc(sizeof(*nvmem_rbm)); + + nvmem_rbm->cell = cell; + nvmem_rbm->reboot.dev = dev; + nvmem_rbm->reboot.write = nvmem_reboot_mode_write; + nvmem_rbm->reboot.priority = 200; + + magicbuf = nvmem_cell_read(nvmem_rbm->cell, &len); + if (IS_ERR(magicbuf) || len != 4) { + dev_err(dev, "error reading reboot mode: %pe\n", magicbuf); + return PTR_ERR(magicbuf); + } + + ret = reboot_mode_register(&nvmem_rbm->reboot, magicbuf, 1); + if (ret) + dev_err(dev, "can't register reboot mode\n"); + + return ret; +} + +static const struct of_device_id nvmem_reboot_mode_of_match[] = { + { .compatible = "nvmem-reboot-mode" }, + { /* sentinel */ } +}; + +static struct driver_d nvmem_reboot_mode_driver = { + .probe = nvmem_reboot_mode_probe, + .name = "nvmem-reboot-mode", + .of_compatible = nvmem_reboot_mode_of_match, +}; +coredevice_platform_driver(nvmem_reboot_mode_driver); diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index ac3a9b048e..097f7d779b 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -175,6 +175,7 @@ int of_regulator_register(struct regulator_dev *rd, struct device_node *node) return PTR_ERR(ri); ri->node = node; + node->dev = rd->dev; if (rd->desc->off_on_delay) ri->enable_time_us = rd->desc->off_on_delay; @@ -197,6 +198,7 @@ static struct regulator_internal *of_regulator_get(struct device_d *dev, const c char *propname; struct regulator_internal *ri; struct device_node *node; + int ret; propname = basprintf("%s-supply", supply); @@ -228,6 +230,10 @@ static struct regulator_internal *of_regulator_get(struct device_d *dev, const c goto out; } + ret = of_device_ensure_probed(node); + if (ret) + return ERR_PTR(ret); + list_for_each_entry(ri, ®ulator_list, list) { if (ri->node == node) { dev_dbg(dev, "Using %s regulator from %s\n", diff --git a/drivers/regulator/fixed.c b/drivers/regulator/fixed.c index 160a55163f..e35b294fb2 100644 --- a/drivers/regulator/fixed.c +++ b/drivers/regulator/fixed.c @@ -20,10 +20,10 @@ #include <of.h> #include <of_gpio.h> #include <gpio.h> +#include <gpiod.h> struct regulator_fixed { int gpio; - int active_low; int always_on; struct regulator_dev rdev; struct regulator_desc rdesc; @@ -36,7 +36,7 @@ static int regulator_fixed_enable(struct regulator_dev *rdev) if (!gpio_is_valid(fix->gpio)) return 0; - return gpio_direction_output(fix->gpio, !fix->active_low); + return gpio_direction_active(fix->gpio, true); } static int regulator_fixed_disable(struct regulator_dev *rdev) @@ -49,7 +49,7 @@ static int regulator_fixed_disable(struct regulator_dev *rdev) if (!gpio_is_valid(fix->gpio)) return 0; - return gpio_direction_output(fix->gpio, fix->active_low); + return gpio_direction_active(fix->gpio, false); } const static struct regulator_ops fixed_ops = { @@ -60,7 +60,6 @@ const static struct regulator_ops fixed_ops = { static int regulator_fixed_probe(struct device_d *dev) { struct regulator_fixed *fix; - enum of_gpio_flags gpioflags; int ret; if (!dev->device_node) @@ -70,14 +69,11 @@ static int regulator_fixed_probe(struct device_d *dev) fix->gpio = -EINVAL; if (of_get_property(dev->device_node, "gpio", NULL)) { - fix->gpio = of_get_named_gpio_flags(dev->device_node, "gpio", 0, &gpioflags); + fix->gpio = gpiod_get(dev, NULL, GPIOD_ASIS); if (fix->gpio < 0) { ret = fix->gpio; goto err; } - - if (gpioflags & OF_GPIO_ACTIVE_LOW) - fix->active_low = 1; } fix->rdesc.ops = &fixed_ops; diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 316ece9e71..9429f107bb 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -27,4 +27,10 @@ config RESET_STM32 help This enables the reset controller driver for STM32MP and STM32 MCUs. +config RESET_STARFIVE + bool "StarFive Controller Driver" if COMPILE_TEST + default SOC_STARFIVE + help + This enables the reset controller driver for the StarFive JH7100. + endif diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 8460c4b154..ce494baae5 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_RESET_CONTROLLER) += core.o obj-$(CONFIG_ARCH_SOCFPGA) += reset-socfpga.o obj-$(CONFIG_RESET_IMX7) += reset-imx7.o obj-$(CONFIG_RESET_STM32) += reset-stm32.o +obj-$(CONFIG_RESET_STARFIVE) += reset-starfive-vic.o diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 26a54f21df..9f5a642539 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -139,35 +139,44 @@ int reset_control_deassert(struct reset_control *rstc) EXPORT_SYMBOL_GPL(reset_control_deassert); /** - * of_reset_control_get - Lookup and obtain a reference to a reset controller. + * of_reset_control_count - Count reset lines + * @node: device node + * + * Returns number of resets, 0 if none specified + */ +static int of_reset_control_count(struct device_node *node) +{ + return of_count_phandle_with_args(node, "resets", "#reset-cells"); +} + +/** + * of_reset_control_get_by_index - Lookup and obtain a reference to a reset controller. * @node: device to be reset by the controller - * @id: reset line name + * @index: reset line index * * Returns a struct reset_control or IS_ERR() condition containing errno. - * - * Use of id names is optional. */ -static struct reset_control *of_reset_control_get(struct device_node *node, - const char *id) +static struct reset_control *of_reset_control_get_by_index(struct device_node *node, + int index) { struct reset_control *rstc; struct reset_controller_dev *r, *rcdev; struct of_phandle_args args; - int index = 0; int rstc_id; int ret; if (!of_get_property(node, "resets", NULL)) return NULL; - if (id) - index = of_property_match_string(node, - "reset-names", id); ret = of_parse_phandle_with_args(node, "resets", "#reset-cells", index, &args); if (ret) return ERR_PTR(ret); + ret = of_device_ensure_probed(args.np); + if (ret) + return ERR_PTR(ret); + rcdev = NULL; list_for_each_entry(r, &reset_controller_list, list) { if (args.np == r->of_node) { @@ -195,6 +204,26 @@ static struct reset_control *of_reset_control_get(struct device_node *node, return rstc; } +/** + * of_reset_control_get - Lookup and obtain a reference to a reset controller. + * @node: device to be reset by the controller + * @id: reset line name + * + * Returns a struct reset_control or IS_ERR() condition containing errno. + * + * Use of id names is optional. + */ +struct reset_control *of_reset_control_get(struct device_node *node, + const char *id) +{ + int index = 0; + + if (id) + index = of_property_match_string(node, "reset-names", id); + + return of_reset_control_get_by_index(node, index); +} + static struct reset_control * gpio_reset_control_get(struct device_d *dev, const char *id) { @@ -302,6 +331,39 @@ int device_reset(struct device_d *dev) } EXPORT_SYMBOL_GPL(device_reset); +int device_reset_all(struct device_d *dev) +{ + struct reset_control *rstc; + int ret, i; + + for (i = 0; i < of_reset_control_count(dev->device_node); i++) { + int ret; + + rstc = of_reset_control_get_by_index(dev->device_node, i); + if (IS_ERR(rstc)) + return PTR_ERR(rstc); + + ret = reset_control_reset(rstc); + if (ret) + return ret; + + reset_control_put(rstc); + } + + if (i == 0) { + rstc = gpio_reset_control_get(dev, NULL); + + ret = reset_control_reset(rstc); + if (ret) + return ret; + + reset_control_put(rstc); + } + + return 0; +} +EXPORT_SYMBOL_GPL(device_reset_all); + int device_reset_us(struct device_d *dev, int us) { struct reset_control *rstc; diff --git a/drivers/reset/reset-starfive-vic.c b/drivers/reset/reset-starfive-vic.c new file mode 100644 index 0000000000..bcf615da0a --- /dev/null +++ b/drivers/reset/reset-starfive-vic.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 Ahmad Fatoum, Pengutronix + * + * StarFive Reset Controller driver + */ +#define pr_fmt(fmt) "reset-starfive: " fmt + +#include <common.h> +#include <init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/reset-controller.h> +#include <soc/starfive/rstgen.h> +#include <dt-bindings/reset-controller/starfive-jh7100.h> +#include <dt-bindings/clock/starfive-jh7100.h> + +struct starfive_rstgen { + void __iomem *base; + struct reset_controller_dev rcdev; + const struct starfive_rstgen_ops *ops; + struct device_node *clknp; + const int *sync_resets; +}; + +static struct starfive_rstgen *to_starfive_rstgen(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct starfive_rstgen, rcdev); +} + +static const int jh7110_rstgen_sync_resets[RSTN_END] = { + [RSTN_SGDMA2P_AHB] = CLK_SGDMA2P_AHB, + [RSTN_SGDMA2P_AXI] = CLK_SGDMA2P_AXI, + [RSTN_DMA2PNOC_AXI] = CLK_DMA2PNOC_AXI, + [RSTN_DLA_AXI] = CLK_DLA_AXI, + [RSTN_DLANOC_AXI] = CLK_DLANOC_AXI, + [RSTN_DLA_APB] = CLK_DLA_APB, + [RSTN_VDECBRG_MAIN] = CLK_VDECBRG_MAIN, + [RSTN_VDEC_AXI] = CLK_VDEC_AXI, + [RSTN_VDEC_BCLK] = CLK_VDEC_BCLK, + [RSTN_VDEC_CCLK] = CLK_VDEC_CCLK, + [RSTN_VDEC_APB] = CLK_VDEC_APB, + [RSTN_JPEG_AXI] = CLK_JPEG_AXI, + [RSTN_JPEG_CCLK] = CLK_JPEG_CCLK, + [RSTN_JPEG_APB] = CLK_JPEG_APB, + [RSTN_JPCGC300_MAIN] = CLK_JPCGC300_MAIN, + [RSTN_GC300_2X] = CLK_GC300_2X, + [RSTN_GC300_AXI] = CLK_GC300_AXI, + [RSTN_GC300_AHB] = CLK_GC300_AHB, + [RSTN_VENC_AXI] = CLK_VENC_AXI, + [RSTN_VENCBRG_MAIN] = CLK_VENCBRG_MAIN, + [RSTN_VENC_BCLK] = CLK_VENC_BCLK, + [RSTN_VENC_CCLK] = CLK_VENC_CCLK, + [RSTN_VENC_APB] = CLK_VENC_APB, + [RSTN_DDRPHY_APB] = CLK_DDRPHY_APB, + [RSTN_USB_AXI] = CLK_USB_AXI, + [RSTN_SGDMA1P_AXI] = CLK_SGDMA1P_AXI, + [RSTN_DMA1P_AXI] = CLK_DMA1P_AXI, + [RSTN_NNE_AHB] = CLK_NNE_AHB, + [RSTN_NNE_AXI] = CLK_NNE_AXI, + [RSTN_NNENOC_AXI] = CLK_NNENOC_AXI, + [RSTN_DLASLV_AXI] = CLK_DLASLV_AXI, + [RSTN_VOUT_SRC] = CLK_VOUT_SRC, + [RSTN_DISP_AXI] = CLK_DISP_AXI, + [RSTN_DISPNOC_AXI] = CLK_DISPNOC_AXI, + [RSTN_SDIO0_AHB] = CLK_SDIO0_AHB, + [RSTN_SDIO1_AHB] = CLK_SDIO1_AHB, + [RSTN_GMAC_AHB] = CLK_GMAC_AHB, + [RSTN_SPI2AHB_AHB] = CLK_SPI2AHB_AHB, + [RSTN_SPI2AHB_CORE] = CLK_SPI2AHB_CORE, + [RSTN_EZMASTER_AHB] = CLK_EZMASTER_AHB, + [RSTN_SEC_AHB] = CLK_SEC_AHB, + [RSTN_AES] = CLK_AES, + [RSTN_PKA] = CLK_PKA, + [RSTN_SHA] = CLK_SHA, + [RSTN_TRNG_APB] = CLK_TRNG_APB, + [RSTN_OTP_APB] = CLK_OTP_APB, + [RSTN_UART0_APB] = CLK_UART0_APB, + [RSTN_UART0_CORE] = CLK_UART0_CORE, + [RSTN_UART1_APB] = CLK_UART1_APB, + [RSTN_UART1_CORE] = CLK_UART1_CORE, + [RSTN_SPI0_APB] = CLK_SPI0_APB, + [RSTN_SPI0_CORE] = CLK_SPI0_CORE, + [RSTN_SPI1_APB] = CLK_SPI1_APB, + [RSTN_SPI1_CORE] = CLK_SPI1_CORE, + [RSTN_I2C0_APB] = CLK_I2C0_APB, + [RSTN_I2C0_CORE] = CLK_I2C0_CORE, + [RSTN_I2C1_APB] = CLK_I2C1_APB, + [RSTN_I2C1_CORE] = CLK_I2C1_CORE, + [RSTN_GPIO_APB] = CLK_GPIO_APB, + [RSTN_UART2_APB] = CLK_UART2_APB, + [RSTN_UART2_CORE] = CLK_UART2_CORE, + [RSTN_UART3_APB] = CLK_UART3_APB, + [RSTN_UART3_CORE] = CLK_UART3_CORE, + [RSTN_SPI2_APB] = CLK_SPI2_APB, + [RSTN_SPI2_CORE] = CLK_SPI2_CORE, + [RSTN_SPI3_APB] = CLK_SPI3_APB, + [RSTN_SPI3_CORE] = CLK_SPI3_CORE, + [RSTN_I2C2_APB] = CLK_I2C2_APB, + [RSTN_I2C2_CORE] = CLK_I2C2_CORE, + [RSTN_I2C3_APB] = CLK_I2C3_APB, + [RSTN_I2C3_CORE] = CLK_I2C3_CORE, + [RSTN_WDTIMER_APB] = CLK_WDTIMER_APB, + [RSTN_WDT] = CLK_WDT_CORE, + [RSTN_VP6INTC_APB] = CLK_VP6INTC_APB, + [RSTN_TEMP_APB] = CLK_TEMP_APB, + [RSTN_TEMP_SENSE] = CLK_TEMP_SENSE, +}; + +static struct clk *starfive_reset_clk_get(struct starfive_rstgen *priv, unsigned id) +{ + struct of_phandle_args clkspec = { + .np = priv->clknp, + .args_count = 1, + }; + + if (!priv->sync_resets || !priv->sync_resets[id]) + return 0; + + clkspec.args[0] = priv->sync_resets[id]; + + pr_debug("synchronous reset=%u clk=%u\n", id, priv->sync_resets[id]); + + return of_clk_get_from_provider(&clkspec); +} + +static int starfive_reset_clk_enable(struct starfive_rstgen *priv, unsigned id) +{ + return clk_enable(starfive_reset_clk_get(priv, id)); +} + +static void starfive_reset_clk_disable(struct starfive_rstgen *priv, unsigned id) +{ + clk_disable(starfive_reset_clk_get(priv, id)); +} + +static int starfive_rstgen(struct starfive_rstgen *priv, unsigned id, bool assert) +{ + void __iomem *base = priv->base; + + __starfive_rstgen(base, id, assert); + + return wait_on_timeout(NSEC_PER_MSEC, __starfive_rstgen_asserted(base, id) == assert); +} + +static int starfive_rstgen_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct starfive_rstgen *priv = to_starfive_rstgen(rcdev); + int ret; + + starfive_reset_clk_enable(priv, id); + ret = starfive_rstgen(priv, id, true); + starfive_reset_clk_disable(priv, id); + + return ret; +} + +static int starfive_rstgen_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct starfive_rstgen *priv = to_starfive_rstgen(rcdev); + int ret; + + starfive_reset_clk_enable(priv, id); + ret = starfive_rstgen(priv, id, false); + starfive_reset_clk_disable(priv, id); + + return ret; +} + +static int starfive_reset(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct starfive_rstgen *priv = to_starfive_rstgen(rcdev); + int ret; + + starfive_reset_clk_enable(priv, id); + + ret = starfive_rstgen(priv, id, true); + if (ret) + goto out; + + udelay(2); + + ret = starfive_rstgen(priv, id, false); + +out: + starfive_reset_clk_disable(priv, id); + + return ret; +} + +static const struct reset_control_ops starfive_rstgen_ops = { + .assert = starfive_rstgen_assert, + .deassert = starfive_rstgen_deassert, + .reset = starfive_reset, +}; + +static int starfive_rstgen_probe(struct device_d *dev) +{ + struct starfive_rstgen *priv; + struct resource *iores; + + priv = xzalloc(sizeof(*priv)); + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + + if ((priv->sync_resets = device_get_match_data(dev))) { + priv->clknp = of_find_compatible_node(NULL, NULL, "starfive,jh7100-clkgen"); + if (!priv->clknp) + return -ENODEV; + } + + priv->base = IOMEM(iores->start); + priv->rcdev.nr_resets = RSTN_END; + priv->rcdev.ops = &starfive_rstgen_ops; + priv->rcdev.of_node = dev->device_node; + + return reset_controller_register(&priv->rcdev); +} + +static const struct of_device_id starfive_rstgen_reset_dt_ids[] = { + { .compatible = "starfive,jh7100-rstgen", .data = jh7110_rstgen_sync_resets }, + { /* sentinel */ }, +}; + +static struct driver_d starfive_rstgen_reset_driver = { + .name = "starfive_rstgen", + .probe = starfive_rstgen_probe, + .of_compatible = starfive_rstgen_reset_dt_ids, +}; +core_platform_driver(starfive_rstgen_reset_driver); diff --git a/drivers/reset/reset-stm32.c b/drivers/reset/reset-stm32.c index a4498f573b..2ef00859c4 100644 --- a/drivers/reset/reset-stm32.c +++ b/drivers/reset/reset-stm32.c @@ -81,8 +81,8 @@ static u32 stm32_reset_status(struct stm32_reset *priv, unsigned long bank) static void stm32_reset(struct stm32_reset *priv, unsigned long id, bool assert) { - int bank = (id / BITS_PER_LONG) * 4; - int offset = id % BITS_PER_LONG; + int bank = (id / 32) * 4; + int offset = id % 32; priv->ops->reset(priv->base + bank, offset, assert); } diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile new file mode 100644 index 0000000000..c3499c0c7f --- /dev/null +++ b/drivers/soc/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-y += imx/ +obj-$(CONFIG_CPU_SIFIVE) += sifive/ +obj-$(CONFIG_SOC_STARFIVE) += starfive/ diff --git a/drivers/soc/sifive/Makefile b/drivers/soc/sifive/Makefile new file mode 100644 index 0000000000..e8113c66f5 --- /dev/null +++ b/drivers/soc/sifive/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SIFIVE_L2) += sifive_l2_cache.o diff --git a/drivers/soc/sifive/sifive_l2_cache.c b/drivers/soc/sifive/sifive_l2_cache.c new file mode 100644 index 0000000000..a1e9a10622 --- /dev/null +++ b/drivers/soc/sifive/sifive_l2_cache.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SiFive L2 cache controller Driver + * + * Copyright (C) 2018-2019 SiFive, Inc. + * + */ + +#define pr_fmt(fmt) "sifive-l2: " fmt + +#include <io.h> +#include <printk.h> +#include <stdio.h> +#include <driver.h> +#include <init.h> +#include <soc/sifive/l2_cache.h> +#include <asm/barrier.h> +#include <linux/bitops.h> + +#define SIFIVE_L2_DIRECCFIX_LOW 0x100 +#define SIFIVE_L2_DIRECCFIX_HIGH 0x104 +#define SIFIVE_L2_DIRECCFIX_COUNT 0x108 + +#define SIFIVE_L2_DIRECCFAIL_LOW 0x120 +#define SIFIVE_L2_DIRECCFAIL_HIGH 0x124 +#define SIFIVE_L2_DIRECCFAIL_COUNT 0x128 + +#define SIFIVE_L2_DATECCFIX_LOW 0x140 +#define SIFIVE_L2_DATECCFIX_HIGH 0x144 +#define SIFIVE_L2_DATECCFIX_COUNT 0x148 + +#define SIFIVE_L2_DATECCFAIL_LOW 0x160 +#define SIFIVE_L2_DATECCFAIL_HIGH 0x164 +#define SIFIVE_L2_DATECCFAIL_COUNT 0x168 + +#define SIFIVE_L2_FLUSH64 0x200 + +#define SIFIVE_L2_CONFIG 0x00 +#define SIFIVE_L2_WAYENABLE 0x08 +#define SIFIVE_L2_ECCINJECTERR 0x40 + +#define SIFIVE_L2_MAX_ECCINTR 4 + +#define MASK_NUM_WAYS GENMASK(15, 8) +#define NUM_WAYS_SHIFT 8 + +#define SIFIVE_L2_FLUSH64_LINE_LEN 64 + +static void __iomem *l2_base = NULL; + +static void sifive_l2_config_read(struct device_d *dev) +{ + u32 regval, val; + + printf("Cache configuration:\n"); + + regval = readl(l2_base + SIFIVE_L2_CONFIG); + val = regval & 0xFF; + printf(" #Banks: %d\n", val); + val = (regval & 0xFF00) >> 8; + printf(" #Ways per bank: %d\n", val); + val = (regval & 0xFF0000) >> 16; + printf(" #Sets per bank: %llu\n", 1llu << val); + val = (regval & 0xFF000000) >> 24; + printf(" #Bytes per cache block: %llu\n", 1llu << val); + + regval = readl(l2_base + SIFIVE_L2_WAYENABLE); + printf(" #Index of the largest way enabled: %d\n", regval); +} + +void sifive_l2_flush64_range(dma_addr_t start, dma_addr_t end) +{ + unsigned long line; + + start = ALIGN_DOWN(start, 64); + end = ALIGN(end, 64); + + if (WARN_ON(!l2_base)) + return; + + if (start == end) + return; + + mb(); + for (line = start; line < end; line += SIFIVE_L2_FLUSH64_LINE_LEN) { + writeq(line, l2_base + SIFIVE_L2_FLUSH64); + mb(); + } +} + +static void sifive_l2_enable_ways(void) +{ + u32 config; + u32 ways; + + config = readl(l2_base + SIFIVE_L2_CONFIG); + ways = (config & MASK_NUM_WAYS) >> NUM_WAYS_SHIFT; + + mb(); + writel(ways - 1, l2_base + SIFIVE_L2_WAYENABLE); + mb(); +} + +static int sifive_l2_probe(struct device_d *dev) +{ + struct resource *iores; + + if (l2_base) + return -EBUSY; + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + + l2_base = IOMEM(iores->start); + + sifive_l2_enable_ways(); + + dev->info = sifive_l2_config_read; + + return 0; +} + +static const struct of_device_id sifive_l2_ids[] = { + { .compatible = "sifive,fu540-c000-ccache" }, + { .compatible = "sifive,fu740-c000-ccache" }, + { .compatible = "starfive,ccache0" }, + { /* end of table */ }, +}; + +static struct driver_d sifive_l2_driver = { + .name = "sfive-l2cache", + .probe = sifive_l2_probe, + .of_compatible = sifive_l2_ids, +}; +postcore_platform_driver(sifive_l2_driver); diff --git a/drivers/soc/starfive/Makefile b/drivers/soc/starfive/Makefile new file mode 100644 index 0000000000..72504b3bef --- /dev/null +++ b/drivers/soc/starfive/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SOC_STARFIVE_JH7100) += jh7100_dma.o diff --git a/drivers/soc/starfive/jh7100_dma.c b/drivers/soc/starfive/jh7100_dma.c new file mode 100644 index 0000000000..a1dc48e73f --- /dev/null +++ b/drivers/soc/starfive/jh7100_dma.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Ahmad Fatoum, Pengutronix + */ + +#include <common.h> +#include <asm/dma.h> +#include <soc/sifive/l2_cache.h> + +#define SDRAM_CACHED_BASE 0x80000000 +#define SDRAM_UNCACHED_BASE 0x1000000000 + +static inline void *jh7100_alloc_coherent(size_t size, dma_addr_t *dma_handle) +{ + dma_addr_t cpu_base; + void *ret; + + ret = xmemalign(PAGE_SIZE, size); + + memset(ret, 0, size); + + cpu_base = (dma_addr_t)ret; + + if (dma_handle) + *dma_handle = cpu_base; + + sifive_l2_flush64_range(cpu_base, cpu_base + size); + + return ret - SDRAM_CACHED_BASE + SDRAM_UNCACHED_BASE; + +} + +static inline void jh7100_free_coherent(void *vaddr, dma_addr_t dma_handle, size_t size) +{ + free((void *)dma_handle); +} + +static const struct dma_ops jh7100_dma_ops = { + .alloc_coherent = jh7100_alloc_coherent, + .free_coherent = jh7100_free_coherent, + .flush_range = sifive_l2_flush64_range, + .inv_range = sifive_l2_flush64_range, +}; + +static int jh7100_dma_init(void) +{ + /* board drivers can claim the machine compatible, so no driver here */ + if (!of_machine_is_compatible("starfive,jh7100")) + return 0; + + dma_set_ops(&jh7100_dma_ops); + + return 0; +} +mmu_initcall(jh7100_dma_init); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 323d93efeb..91083ee709 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -71,7 +71,7 @@ config DRIVER_SPI_IMX_0_7 config DRIVER_SPI_IMX_2_3 bool - depends on ARCH_IMX50 || ARCH_IMX51 || ARCH_IMX53 || ARCH_IMX6 || ARCH_IMX7 || ARCH_IMX8MQ + depends on ARCH_IMX50 || ARCH_IMX51 || ARCH_IMX53 || ARCH_IMX6 || ARCH_IMX7 || ARCH_IMX8M default y config DRIVER_SPI_MXS diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 8421d9d7c1..d1d3bdcc41 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -107,6 +107,8 @@ struct spi_device *spi_new_device(struct spi_controller *ctrl, if (status) goto fail; + chip->device_node->dev = &proxy->dev; + return proxy; fail: free(proxy); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f416acc999..49deaf4d98 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -643,14 +643,14 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action) /* Initialize the TRB ring */ dep->trb_dequeue = 0; dep->trb_enqueue = 0; - memset(dep->trb_pool, 0, + memset_io(dep->trb_pool, 0, sizeof(struct dwc3_trb) * DWC3_TRB_NUM); /* Link TRB. The HWO bit is never reset */ trb_st_hw = &dep->trb_pool[0]; trb_link = &dep->trb_pool[DWC3_TRB_NUM - 1]; - memset(trb_link, 0, sizeof(*trb_link)); + memset_io(trb_link, 0, sizeof(*trb_link)); trb_link->bpl = lower_32_bits(dwc3_trb_dma_offset(dep, trb_st_hw)); @@ -2908,10 +2908,10 @@ static void dwc3_gadget_poll(struct usb_gadget * g) buf = xzalloc(count); amount = min(count, evt->length - evt->lpos); - memcpy(buf, evt->buf + evt->lpos, amount); + memcpy_fromio(buf, evt->buf + evt->lpos, amount); if (amount < count) - memcpy(buf + amount, evt->buf, count - amount); + memcpy_fromio(buf + amount, evt->buf, count - amount); evt->lpos = (evt->lpos + count) % evt->length; diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 4ed6cbbee1..574a6821ad 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -36,10 +36,12 @@ config USB_GADGET_AUTOSTART bool default y prompt "Automatically start usbgadget on boot" + select SYSTEM_PARTITIONS if USB_GADGET_MASS_STORAGE help Enabling this option allows to automatically start a dfu or fastboot gadget during boot. This behaviour is controlled with - the global.usbgadget.dfu_function and global.fastboot.* variables. + the global.usbgadget.dfu_function, global.system.partitions + and global.fastboot.* variables. comment "USB Gadget drivers" @@ -57,4 +59,15 @@ config USB_GADGET_FASTBOOT select BANNER select FASTBOOT_BASE prompt "Android Fastboot USB Gadget" + +config USB_GADGET_MASS_STORAGE + bool + select BTHREAD + prompt "USB Mass Storage Gadget" + help + The Mass Storage Gadget acts as a USB Mass Storage disk drive. + As its storage repository it can use a regular file or a block + device. Multiple storages can be specified at once on + instantiation time. + endif diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 27673fcf0e..5ba4920c08 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_USB_GADGET) += composite.o config.o usbstring.o epautoconf.o udc-co obj-$(CONFIG_USB_GADGET_SERIAL) += u_serial.o serial.o f_serial.o f_acm.o obj-$(CONFIG_USB_GADGET_DFU) += dfu.o obj-$(CONFIG_USB_GADGET_FASTBOOT) += f_fastboot.o +obj-$(CONFIG_USB_GADGET_MASS_STORAGE) += f_mass_storage.o storage_common.o obj-$(CONFIG_USB_GADGET_DRIVER_ARC) += fsl_udc.o pbl-$(CONFIG_USB_GADGET_DRIVER_ARC_PBL) += fsl_udc_pbl.o obj-$(CONFIG_USB_GADGET_DRIVER_AT91) += at91_udc.o diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c new file mode 100644 index 0000000000..753042125d --- /dev/null +++ b/drivers/usb/gadget/f_mass_storage.c @@ -0,0 +1,2714 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * f_mass_storage.c -- Mass Storage USB Composite Function + * + * Copyright (C) 2003-2008 Alan Stern + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz <m.nazarewicz@samsung.com> + * All rights reserved. + */ + +/* + * The Mass Storage Function acts as a USB Mass Storage device, + * appearing to the host as a disk drive or as a CD-ROM drive. In + * addition to providing an example of a genuinely useful composite + * function for a USB device, it also illustrates a technique of + * double-buffering for increased throughput. + * + * Function supports multiple logical units (LUNs). Backing storage + * for each LUN is provided by a regular file or a block device. + * Access for each LUN can be limited to read-only. Moreover, the + * function can indicate that LUN is removable and/or CD-ROM. (The + * later implies read-only access.) + * + * MSF is configured by specifying a fsg_config structure. It has the + * following fields: + * + * nluns Number of LUNs function have (anywhere from 1 + * to FSG_MAX_LUNS which is 8). + * luns An array of LUN configuration values. This + * should be filled for each LUN that + * function will include (ie. for "nluns" + * LUNs). Each element of the array has + * the following fields: + * ->filename The path to the backing file for the LUN. + * Required if LUN is not marked as + * removable. + * ->ro Flag specifying access to the LUN shall be + * read-only. This is implied if CD-ROM + * emulation is enabled as well as when + * it was impossible to open "filename" + * in R/W mode. + * ->removable Flag specifying that LUN shall be indicated as + * being removable. + * ->cdrom Flag specifying that LUN shall be reported as + * being a CD-ROM. + * + * vendor_name + * product_name + * release Information used as a reply to INQUIRY + * request. To use default set to NULL, + * NULL, 0xffff respectively. The first + * field should be 8 and the second 16 + * characters or less. + * + * can_stall Set to permit function to halt bulk endpoints. + * Disabled on some USB devices known not + * to work correctly. You should set it + * to true. + * + * If "removable" is not set for a LUN then a backing file must be + * specified. If it is set, then NULL filename means the LUN's medium + * is not loaded (an empty string as "filename" in the fsg_config + * structure causes error). The CD-ROM emulation includes a single + * data track and no audio tracks; hence there need be only one + * backing file per LUN. Note also that the CD-ROM block length is + * set to 512 rather than the more common value 2048. + * + * + * MSF includes support for module parameters. If gadget using it + * decides to use it, the following module parameters will be + * available: + * + * file=filename[,filename...] + * Names of the files or block devices used for + * backing storage. + * ro=b[,b...] Default false, boolean for read-only access. + * removable=b[,b...] + * Default true, boolean for removable media. + * cdrom=b[,b...] Default false, boolean for whether to emulate + * a CD-ROM drive. + * luns=N Default N = number of filenames, number of + * LUNs to support. + * stall Default determined according to the type of + * USB device controller (usually true), + * boolean to permit the driver to halt + * bulk endpoints. + * + * The module parameters may be prefixed with some string. You need + * to consult gadget's documentation or source to verify whether it is + * using those module parameters and if it does what are the prefixes + * (look for FSG_MODULE_PARAMETERS() macro usage, what's inside it is + * the prefix). + * + * + * Requirements are modest; only a bulk-in and a bulk-out endpoint are + * needed. The memory requirement amounts to two 16K buffers, size + * configurable by a parameter. Support is included for both + * full-speed and high-speed operation. + * + * Note that the driver is slightly non-portable in that it assumes a + * single memory/DMA buffer will be useable for bulk-in, bulk-out, and + * interrupt-in endpoints. With most device controllers this isn't an + * issue, but there may be some with hardware restrictions that prevent + * a buffer from being used by more than one endpoint. + * + * When a LUN receive an "eject" SCSI request (Start/Stop Unit), + * if the LUN is removable, the backing file is released to simulate + * ejection. + * + * + * This function is heavily based on "File-backed Storage Gadget" by + * Alan Stern which in turn is heavily based on "Gadget Zero" by David + * Brownell. The driver's SCSI command interface was based on the + * "Information technology - Small Computer System Interface - 2" + * document from X3T9.2 Project 375D, Revision 10L, 7-SEP-93, + * available at <http://www.t10.org/ftp/t10/drafts/s2/s2-r10l.pdf>. + * The single exception is opcode 0x23 (READ FORMAT CAPACITIES), which + * was based on the "Universal Serial Bus Mass Storage Class UFI + * Command Specification" document, Revision 1.0, December 14, 1998, + * available at + * <http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf>. + */ + +/* + * Driver Design + * + * The MSF is fairly straightforward. There is a main kernel + * thread that handles most of the work. Interrupt routines field + * callbacks from the controller driver: bulk- and interrupt-request + * completion notifications, endpoint-0 events, and disconnect events. + * Completion events are passed to the main thread by wakeup calls. Many + * ep0 requests are handled at interrupt time, but SetInterface, + * SetConfiguration, and device reset requests are forwarded to the + * thread in the form of "exceptions" using SIGUSR1 signals (since they + * should interrupt any ongoing file I/O operations). + * + * The thread's main routine implements the standard command/data/status + * parts of a SCSI interaction. It and its subroutines are full of tests + * for pending signals/exceptions -- all this polling is necessary since + * the kernel has no setjmp/longjmp equivalents. (Maybe this is an + * indication that the driver really wants to be running in userspace.) + * An important point is that so long as the thread is alive it keeps an + * open reference to the backing file. This will prevent unmounting + * the backing file's underlying filesystem and could cause problems + * during system shutdown, for example. To prevent such problems, the + * thread catches INT, TERM, and KILL signals and converts them into + * an EXIT exception. + * + * In normal operation the main thread is started during the gadget's + * fsg_bind() callback and stopped during fsg_unbind(). But it can + * also exit when it receives a signal, and there's no point leaving + * the gadget running when the thread is dead. At of this moment, MSF + * provides no way to deregister the gadget when thread dies -- maybe + * a callback functions is needed. + * + * To provide maximum throughput, the driver uses a circular pipeline of + * buffer heads (struct fsg_buffhd). In principle the pipeline can be + * arbitrarily long; in practice the benefits don't justify having more + * than 2 stages (i.e., double buffering). But it helps to think of the + * pipeline as being a long one. Each buffer head contains a bulk-in and + * a bulk-out request pointer (since the buffer can be used for both + * output and input -- directions always are given from the host's + * point of view) as well as a pointer to the buffer and various state + * variables. + * + * Use of the pipeline follows a simple protocol. There is a variable + * (fsg->next_buffhd_to_fill) that points to the next buffer head to use. + * At any time that buffer head may still be in use from an earlier + * request, so each buffer head has a state variable indicating whether + * it is EMPTY, FULL, or BUSY. Typical use involves waiting for the + * buffer head to be EMPTY, filling the buffer either by file I/O or by + * USB I/O (during which the buffer head is BUSY), and marking the buffer + * head FULL when the I/O is complete. Then the buffer will be emptied + * (again possibly by USB I/O, during which it is marked BUSY) and + * finally marked EMPTY again (possibly by a completion routine). + * + * A module parameter tells the driver to avoid stalling the bulk + * endpoints wherever the transport specification allows. This is + * necessary for some UDCs like the SuperH, which cannot reliably clear a + * halt on a bulk endpoint. However, under certain circumstances the + * Bulk-only specification requires a stall. In such cases the driver + * will halt the endpoint and set a flag indicating that it should clear + * the halt in software during the next device reset. Hopefully this + * will permit everything to work correctly. Furthermore, although the + * specification allows the bulk-out endpoint to halt when the host sends + * too much data, implementing this would cause an unavoidable race. + * The driver will always use the "no-stall" approach for OUT transfers. + * + * One subtle point concerns sending status-stage responses for ep0 + * requests. Some of these requests, such as device reset, can involve + * interrupting an ongoing file I/O operation, which might take an + * arbitrarily long time. During that delay the host might give up on + * the original ep0 request and issue a new one. When that happens the + * driver should not notify the host about completion of the original + * request, as the host will no longer be waiting for it. So the driver + * assigns to each ep0 request a unique tag, and it keeps track of the + * tag value of the request associated with a long-running exception + * (device-reset, interface-change, or configuration-change). When the + * exception handler is finished, the status-stage response is submitted + * only if the current ep0 request tag is equal to the exception request + * tag. Thus only the most recently received ep0 request will get a + * status-stage response. + * + * Warning: This driver source file is too long. It ought to be split up + * into a header file plus about 3 separate .c files, to handle the details + * of the Gadget, USB Mass Storage, and SCSI protocols. + */ + +/* #define VERBOSE_DEBUG */ +/* #define DUMP_MSGS */ + +#define pr_fmt(fmt) "f_ums: " fmt + +#include <common.h> +#include <unistd.h> +#include <linux/stat.h> +#include <linux/wait.h> +#include <fcntl.h> +#include <file-list.h> +#include <dma.h> +#include <linux/bug.h> +#include <linux/rwsem.h> +#include <linux/pagemap.h> +#include <disks.h> +#include <scsi.h> + +#include <linux/err.h> +#include <usb/mass_storage.h> + +#include <asm/unaligned.h> +#include <linux/bitops.h> +#include <usb/gadget.h> +#include <usb/composite.h> +#include <linux/bitmap.h> +#include <linux/completion.h> +#include <bthread.h> +#include <sched.h> + + +/*------------------------------------------------------------------------*/ + +#define FSG_DRIVER_DESC "ums" +#define UMS_NAME_LEN 16 + +#define FSG_DRIVER_VERSION "2012/06/5" + +static const char fsg_string_interface[] = "Mass Storage"; + +#include "storage_common.h" + +/* Static strings, in UTF-8 (for simplicity we use only ASCII characters) */ +struct usb_string fsg_strings[] = { + {FSG_STRING_INTERFACE, fsg_string_interface}, + {} +}; + +static struct usb_gadget_strings fsg_stringtab = { + .language = 0x0409, /* en-us */ + .strings = fsg_strings, +}; + +/*-------------------------------------------------------------------------*/ + +struct bthread *thread_task; + +struct kref {int x; }; + +struct fsg_dev; + +static struct file_list *ums_files; + +/* Data shared by all the FSG instances. */ +struct fsg_common { + struct usb_gadget *gadget; + struct fsg_dev *fsg, *new_fsg; + + struct usb_ep *ep0; /* Copy of gadget->ep0 */ + struct usb_request *ep0req; /* Copy of cdev->req */ + unsigned int ep0_req_tag; + + struct fsg_buffhd *next_buffhd_to_fill; + struct fsg_buffhd *next_buffhd_to_drain; + struct fsg_buffhd buffhds[FSG_NUM_BUFFERS]; + + int cmnd_size; + u8 cmnd[MAX_COMMAND_SIZE]; + + unsigned int nluns; + unsigned int lun; + struct fsg_lun luns[FSG_MAX_LUNS]; + + unsigned int bulk_out_maxpacket; + enum fsg_state state; /* For exception handling */ + unsigned int exception_req_tag; + + enum data_direction data_dir; + u32 data_size; + u32 data_size_from_cmnd; + u32 tag; + u32 residue; + u32 usb_amount_left; + + unsigned int can_stall:1; + unsigned int phase_error:1; + unsigned int short_packet_received:1; + unsigned int bad_lun_okay:1; + unsigned int running:1; + + struct completion thread_wakeup_needed; + + /* Callback functions. */ + const struct fsg_operations *ops; + /* Gadget's private data. */ + void *private_data; + + const char *vendor_name; /* 8 characters or less */ + const char *product_name; /* 16 characters or less */ + u16 release; + + /* Vendor (8 chars), product (16 chars), release (4 + * hexadecimal digits) and NUL byte */ + char inquiry_string[8 + 16 + 4 + 1]; +}; + +struct fsg_config { + unsigned nluns; + struct fsg_lun_config { + const char *filename; + char ro; + char removable; + char cdrom; + char nofua; + } luns[FSG_MAX_LUNS]; + + /* Callback functions. */ + const struct fsg_operations *ops; + /* Gadget's private data. */ + void *private_data; + + const char *vendor_name; /* 8 characters or less */ + const char *product_name; /* 16 characters or less */ + + char can_stall; +}; + +struct fsg_dev { + struct usb_function function; + struct usb_gadget *gadget; /* Copy of cdev->gadget */ + struct fsg_common *common; + + u16 interface_number; + + unsigned int bulk_in_enabled:1; + unsigned int bulk_out_enabled:1; + + unsigned long atomic_bitflags; +#define IGNORE_BULK_OUT 0 + + struct usb_ep *bulk_in; + struct usb_ep *bulk_out; +}; + + +static inline int __fsg_is_set(struct fsg_common *common, + const char *func, unsigned line) +{ + if (common->fsg) + return 1; + ERROR(common, "common->fsg is NULL in %s at %u\n", func, line); + WARN_ON(1); + + return 0; +} + +#define fsg_is_set(common) likely(__fsg_is_set(common, __func__, __LINE__)) + + +static inline struct fsg_dev *fsg_from_func(struct usb_function *f) +{ + return container_of(f, struct fsg_dev, function); +} + +static inline struct f_ums_opts * +fsg_opts_from_func_inst(const struct usb_function_instance *fi) +{ + return container_of(fi, struct f_ums_opts, func_inst); +} + +typedef void (*fsg_routine_t)(struct fsg_dev *); + +static int exception_in_progress(struct fsg_common *common) +{ + return common->state > FSG_STATE_IDLE; +} + +/* Make bulk-out requests be divisible by the maxpacket size */ +static void set_bulk_out_req_length(struct fsg_common *common, + struct fsg_buffhd *bh, unsigned int length) +{ + unsigned int rem; + + bh->bulk_out_intended_length = length; + rem = length % common->bulk_out_maxpacket; + if (rem > 0) + length += common->bulk_out_maxpacket - rem; + bh->outreq->length = length; +} + +/*-------------------------------------------------------------------------*/ + +static struct f_ums_opts ums[14]; // FIXME +static int ums_count; + +static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) +{ + const char *name; + + if (ep == fsg->bulk_in) + name = "bulk-in"; + else if (ep == fsg->bulk_out) + name = "bulk-out"; + else + name = ep->name; + DBG(fsg, "%s set halt\n", name); + return usb_ep_set_halt(ep); +} + +/*-------------------------------------------------------------------------*/ + +/* These routines may be called in process context or in_irq */ + +/* Caller must hold fsg->lock */ +static void wakeup_thread(struct fsg_common *common) +{ + complete(&common->thread_wakeup_needed); +} + +static void report_exception(const char *prefix, enum fsg_state state) +{ + const char *msg = "<unknown>"; + switch (state) { + /* This one isn't used anywhere */ + case FSG_STATE_COMMAND_PHASE: + msg = "Command Phase"; + break; + case FSG_STATE_DATA_PHASE: + msg = "Data Phase"; + break; + case FSG_STATE_STATUS_PHASE: + msg = "Status Phase"; + break; + + case FSG_STATE_IDLE: + msg = "Idle"; + break; + case FSG_STATE_ABORT_BULK_OUT: + msg = "abort bulk out"; + break; + case FSG_STATE_RESET: + msg = "reset"; + break; + case FSG_STATE_INTERFACE_CHANGE: + msg = "interface change"; + break; + case FSG_STATE_CONFIG_CHANGE: + msg = "config change"; + break; + case FSG_STATE_DISCONNECT: + msg = "disconnect"; + break; + case FSG_STATE_EXIT: + msg = "exit"; + break; + case FSG_STATE_TERMINATED: + msg = "terminated"; + break; + } + + pr_debug("%s: %s\n", prefix, msg); +} + +static void raise_exception(struct fsg_common *common, enum fsg_state new_state) +{ + /* Do nothing if a higher-priority exception is already in progress. + * If a lower-or-equal priority exception is in progress, preempt it + * and notify the main thread by sending it a signal. */ + if (common->state <= new_state) { + report_exception("raising", new_state); + common->exception_req_tag = common->ep0_req_tag; + common->state = new_state; + wakeup_thread(common); + } +} + +/*-------------------------------------------------------------------------*/ + +static int ep0_queue(struct fsg_common *common) +{ + int rc; + + rc = usb_ep_queue(common->ep0, common->ep0req); + common->ep0->driver_data = common; + if (rc != 0 && rc != -ESHUTDOWN) { + /* We can't do much more than wait for a reset */ + WARNING(common, "error in submission: %s --> %d\n", + common->ep0->name, rc); + } + return rc; +} + +/*-------------------------------------------------------------------------*/ + +/* Bulk and interrupt endpoint completion handlers. + * These always run in_irq. */ + +static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_common *common = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + if (req->status || req->actual != req->length) + DBG(common, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) /* Request was cancelled */ + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + bh->inreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(common); +} + +static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_common *common = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + dump_msg(common, "bulk-out", req->buf, req->actual); + if (req->status || req->actual != bh->bulk_out_intended_length) + DBG(common, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, + bh->bulk_out_intended_length); + if (req->status == -ECONNRESET) /* Request was cancelled */ + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + bh->outreq_busy = 0; + bh->state = BUF_STATE_FULL; + wakeup_thread(common); +} + +/*-------------------------------------------------------------------------*/ + +/* Ep0 class-specific handlers. These always run in_irq. */ + +static int fsg_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct usb_request *req = fsg->common->ep0req; + u16 w_index = get_unaligned_le16(&ctrl->wIndex); + u16 w_value = get_unaligned_le16(&ctrl->wValue); + u16 w_length = get_unaligned_le16(&ctrl->wLength); + + if (!fsg_is_set(fsg->common)) + return -EOPNOTSUPP; + + switch (ctrl->bRequest) { + + case US_BULK_RESET_REQUEST: + if (ctrl->bRequestType != + (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != fsg->interface_number || w_value != 0) + return -EDOM; + + /* Raise an exception to stop the current operation + * and reinitialize our state. */ + DBG(fsg, "bulk reset request\n"); + raise_exception(fsg->common, FSG_STATE_RESET); + return DELAYED_STATUS; + + case US_BULK_GET_MAX_LUN: + if (ctrl->bRequestType != + (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != fsg->interface_number || w_value != 0) + return -EDOM; + VDBG(fsg, "get max LUN\n"); + *(u8 *) req->buf = fsg->common->nluns - 1; + + /* Respond with data/status */ + req->length = min((u16)1, w_length); + return ep0_queue(fsg->common); + } + + VDBG(fsg, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + get_unaligned_le16(&ctrl->wValue), w_index, w_length); + return -EOPNOTSUPP; +} + +/*-------------------------------------------------------------------------*/ + +/* All the following routines run in process context */ + +/* Use this for bulk or interrupt transfers, not ep0 */ +static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request *req, int *pbusy, + enum fsg_buffer_state *state) +{ + int rc; + + if (ep == fsg->bulk_in) + dump_msg(fsg, "bulk-in", req->buf, req->length); + + *pbusy = 1; + *state = BUF_STATE_BUSY; + rc = usb_ep_queue(ep, req); + if (rc != 0) { + *pbusy = 0; + *state = BUF_STATE_EMPTY; + + /* We can't do much more than wait for a reset */ + + /* Note: currently the net2280 driver fails zero-length + * submissions if DMA is enabled. */ + if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && + req->length == 0)) + WARNING(fsg, "error in submission: %s --> %d\n", + ep->name, rc); + } +} + +#define START_TRANSFER_OR(common, ep_name, req, pbusy, state) \ + if (fsg_is_set(common)) \ + start_transfer((common)->fsg, (common)->fsg->ep_name, \ + req, pbusy, state); \ + else + +#define START_TRANSFER(common, ep_name, req, pbusy, state) \ + START_TRANSFER_OR(common, ep_name, req, pbusy, state) (void)0 + +static int sleep_thread(struct fsg_common *common) +{ + int ret; + + /* Wait until a signal arrives or we are woken up */ + ret = wait_for_completion_interruptible(&common->thread_wakeup_needed); + if (ret) + return ret; + + reinit_completion(&common->thread_wakeup_needed); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int do_read(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset; + unsigned int amount; + unsigned int partial_page; + ssize_t nread; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (common->cmnd[0] == SCSI_READ6) + lba = get_unaligned_be24(&common->cmnd[1]); + else { + lba = get_unaligned_be32(&common->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = don't read from the + * cache), but we don't implement them. */ + if ((common->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + file_offset = ((loff_t) lba) << 9; + + /* Carry out the file reads */ + amount_left = common->data_size_from_cmnd; + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply */ + + for (;;) { + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. */ + amount = min(amount_left, FSG_BUFLEN); + partial_page = file_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, (unsigned int) PAGE_CACHE_SIZE - + partial_page); + + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + nread = pread(ums[common->lun].fd, bh->buf, amount, file_offset); + + VLDBG(curlun, "file read %u @ %llu -> %zd\n", amount, + (unsigned long long) file_offset, + nread); + if (nread <= 0) { + const char *err = nread ? strerror(-nread) : "EOF"; + LDBG(curlun, "error in file read: %s\n", err); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); /* Round down to a block */ + } + file_offset += nread; + amount_left -= nread; + common->residue -= nread; + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; /* No more left to read */ + + /* Send this buffer and go read some more */ + bh->inreq->zero = 0; + START_TRANSFER_OR(common, bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state) + /* Don't know what to do if + * common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + } + + return -EIO; /* No default reply */ +} + +/*-------------------------------------------------------------------------*/ + +static int do_write(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u32 lba; + struct fsg_buffhd *bh; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + loff_t usb_offset, file_offset; + unsigned int amount; + unsigned int partial_page; + ssize_t nwritten; + int rc; + + if (curlun->ro) { + curlun->sense_data = SS_WRITE_PROTECTED; + return -EINVAL; + } + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (common->cmnd[0] == SCSI_WRITE6) + lba = get_unaligned_be24(&common->cmnd[1]); + else { + lba = get_unaligned_be32(&common->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = write directly to the + * medium). We don't implement DPO; we implement FUA by + * performing synchronous output. */ + if (common->cmnd[1] & ~0x18) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* Carry out the file writes */ + get_some_more = 1; + file_offset = usb_offset = ((loff_t) lba) << 9; + amount_left_to_req = common->data_size_from_cmnd; + amount_left_to_write = common->data_size_from_cmnd; + + while (amount_left_to_write > 0) { + + /* Queue a request for more data from the host */ + bh = common->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + + /* Figure out how much we want to get: + * Try to get the remaining amount. + * But don't get more than the buffer size. + * And don't try to go past the end of the file. + * If we're not at a page boundary, + * don't go past the next page. + * If this means getting 0, then we were asked + * to write past the end of file. + * Finally, round down to a block boundary. */ + amount = min(amount_left_to_req, FSG_BUFLEN); + partial_page = usb_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, + (unsigned int) PAGE_CACHE_SIZE - partial_page); + + if (amount == 0) { + get_some_more = 0; + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->info_valid = 1; + continue; + } + amount -= (amount & 511); + if (amount == 0) { + + /* Why were we were asked to transfer a + * partial block? */ + get_some_more = 0; + continue; + } + + /* Get the next buffer */ + usb_offset += amount; + common->usb_amount_left -= amount; + amount_left_to_req -= amount; + if (amount_left_to_req == 0) + get_some_more = 0; + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + bh->outreq->length = amount; + bh->bulk_out_intended_length = amount; + bh->outreq->short_not_ok = 1; + START_TRANSFER_OR(common, bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state) + /* Don't know what to do if + * common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + continue; + } + + /* Write the received data to the backing file */ + bh = common->next_buffhd_to_drain; + if (bh->state == BUF_STATE_EMPTY && !get_some_more) + break; /* We stopped early */ + if (bh->state == BUF_STATE_FULL) { + common->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) { + curlun->sense_data = SS_COMMUNICATION_FAILURE; + curlun->info_valid = 1; + break; + } + + amount = bh->outreq->actual; + + /* Perform the write */ + nwritten = pwrite(ums[common->lun].fd, bh->buf, amount, file_offset); + + VLDBG(curlun, "file write %u @ %llu -> %zd\n", amount, + (unsigned long long) file_offset, + nwritten); + + if (nwritten < 0) { + LDBG(curlun, "error in file write: %pe\n", ERR_PTR(nwritten)); + nwritten = 0; + } else if (nwritten < amount) { + LDBG(curlun, "partial file write: %d/%u\n", + (int) nwritten, amount); + nwritten -= (nwritten & 511); + /* Round down to a block */ + } + file_offset += nwritten; + amount_left_to_write -= nwritten; + common->residue -= nwritten; + + /* If an error occurred, report it and its position */ + if (nwritten < amount) { + pr_warn("nwritten:%zd amount:%u\n", nwritten, + amount); + curlun->sense_data = SS_WRITE_ERROR; + curlun->info_valid = 1; + break; + } + + /* Did the host decide to stop early? */ + if (bh->outreq->actual != bh->outreq->length) { + common->short_packet_received = 1; + break; + } + continue; + } + + /* Wait for something to happen */ + rc = sleep_thread(common); + if (rc) + return rc; + } + + return -EIO; /* No default reply */ +} + +/*-------------------------------------------------------------------------*/ + +static int do_synchronize_cache(struct fsg_common *common) +{ + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int do_verify(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u32 lba; + u32 verification_length; + struct fsg_buffhd *bh = common->next_buffhd_to_fill; + loff_t file_offset; + u32 amount_left; + unsigned int amount; + ssize_t nread; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + lba = get_unaligned_be32(&common->cmnd[2]); + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) but we don't implement it. */ + if (common->cmnd[1] & ~0x10) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + verification_length = get_unaligned_be16(&common->cmnd[7]); + if (unlikely(verification_length == 0)) + return -EIO; /* No default reply */ + + /* Prepare to carry out the file verify */ + amount_left = verification_length << 9; + file_offset = ((loff_t) lba) << 9; + + /* Write out all the dirty buffers before invalidating them */ + + /* Just try to read the requested blocks */ + while (amount_left > 0) { + + /* Figure out how much we need to read: + * Try to read the remaining amount, but not more than + * the buffer size. + * And don't try to read past the end of the file. + * If this means reading 0 then we were asked to read + * past the end of file. */ + amount = min(amount_left, FSG_BUFLEN); + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->info_valid = 1; + break; + } + + /* Perform the read */ + nread = pread(ums[common->lun].fd, bh->buf, amount, file_offset); + + VLDBG(curlun, "file read %u @ %llu -> %zd\n", amount, + (unsigned long long) file_offset, + nread); + if (nread <= 0) { + const char *err = nread ? strerror(-nread) : "EOF"; + LDBG(curlun, "error in file read: %s\n", err); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file verify: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); /* Round down to a sector */ + } + if (nread == 0) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->info_valid = 1; + break; + } + file_offset += nread; + amount_left -= nread; + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + static const char vendor_id[] = "Linux "; + u8 *buf = (u8 *) bh->buf; + + if (!curlun) { /* Unsupported LUNs are okay */ + common->bad_lun_okay = 1; + memset(buf, 0, 36); + buf[0] = 0x7f; /* Unsupported, no device-type */ + buf[4] = 31; /* Additional length */ + return 36; + } + + memset(buf, 0, 8); + buf[0] = TYPE_DISK; + buf[1] = curlun->removable ? 0x80 : 0; + buf[2] = 2; /* ANSI SCSI level 2 */ + buf[3] = 2; /* SCSI-2 INQUIRY data format */ + buf[4] = 31; /* Additional length */ + /* No special options */ + sprintf((char *) (buf + 8), "%-8s%-16s%04x", (char*) vendor_id , + ums[common->lun].name, (u16) 0xffff); + + return 36; +} + + +static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u8 *buf = (u8 *) bh->buf; + u32 sd, sdinfo = 0; + int valid; + + /* + * From the SCSI-2 spec., section 7.9 (Unit attention condition): + * + * If a REQUEST SENSE command is received from an initiator + * with a pending unit attention condition (before the target + * generates the contingent allegiance condition), then the + * target shall either: + * a) report any pending sense data and preserve the unit + * attention condition on the logical unit, or, + * b) report the unit attention condition, may discard any + * pending sense data, and clear the unit attention + * condition on the logical unit for that initiator. + * + * FSG normally uses option a); enable this code to use option b). + */ +#if 0 + if (curlun && curlun->unit_attention_data != SS_NO_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + } +#endif + + if (!curlun) { /* Unsupported LUNs are okay */ + common->bad_lun_okay = 1; + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + valid = 0; + } else { + sd = curlun->sense_data; + valid = curlun->info_valid << 7; + curlun->sense_data = SS_NO_SENSE; + curlun->info_valid = 0; + } + + memset(buf, 0, 18); + buf[0] = valid | 0x70; /* Valid, current error */ + buf[2] = SK(sd); + put_unaligned_be32(sdinfo, &buf[3]); /* Sense information */ + buf[7] = 18 - 8; /* Additional sense length */ + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + return 18; +} + +static int do_read_capacity(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u32 lba = get_unaligned_be32(&common->cmnd[2]); + int pmi = common->cmnd[8]; + u8 *buf = (u8 *) bh->buf; + + /* Check the PMI and LBA fields */ + if (pmi > 1 || (pmi == 0 && lba != 0)) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + put_unaligned_be32(curlun->num_sectors - 1, &buf[0]); + /* Max logical block */ + put_unaligned_be32(512, &buf[4]); /* Block length */ + return 8; +} + +static int do_read_header(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + int msf = common->cmnd[1] & 0x02; + u32 lba = get_unaligned_be32(&common->cmnd[2]); + u8 *buf = (u8 *) bh->buf; + + if (common->cmnd[1] & ~0x02) { /* Mask away MSF */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + memset(buf, 0, 8); + buf[0] = 0x01; /* 2048 bytes of user data, rest is EC */ + store_cdrom_address(&buf[4], msf, lba); + return 8; +} + + +static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + int msf = common->cmnd[1] & 0x02; + int start_track = common->cmnd[6]; + u8 *buf = (u8 *) bh->buf; + + if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ + start_track > 1) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + memset(buf, 0, 20); + buf[1] = (20-2); /* TOC data length */ + buf[2] = 1; /* First track number */ + buf[3] = 1; /* Last track number */ + buf[5] = 0x16; /* Data track, copying allowed */ + buf[6] = 0x01; /* Only track is number 1 */ + store_cdrom_address(&buf[8], msf, 0); + + buf[13] = 0x16; /* Lead-out track is data */ + buf[14] = 0xAA; /* Lead-out track number */ + store_cdrom_address(&buf[16], msf, curlun->num_sectors); + + return 20; +} + +static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + int mscmnd = common->cmnd[0]; + u8 *buf = (u8 *) bh->buf; + u8 *buf0 = buf; + int pc, page_code; + int changeable_values, all_pages; + int valid_page = 0; + int len, limit; + + if ((common->cmnd[1] & ~0x08) != 0) { /* Mask away DBD */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + pc = common->cmnd[2] >> 6; + page_code = common->cmnd[2] & 0x3f; + if (pc == 3) { + curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + return -EINVAL; + } + changeable_values = (pc == 1); + all_pages = (page_code == 0x3f); + + /* Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. */ + memset(buf, 0, 8); + if (mscmnd == SCSI_MODE_SEN6) { + buf[2] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 4; + limit = 255; + } else { /* SCSI_MODE_SEN10 */ + buf[3] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 8; + limit = 65535; /* Should really be FSG_BUFLEN */ + } + + /* No block descriptors */ + + /* The mode pages, in numerical order. The only page we support + * is the Caching page. */ + if (page_code == 0x08 || all_pages) { + valid_page = 1; + buf[0] = 0x08; /* Page code */ + buf[1] = 10; /* Page length */ + memset(buf+2, 0, 10); /* None of the fields are changeable */ + + if (!changeable_values) { + buf[2] = 0x04; /* Write cache enable, */ + /* Read cache not disabled */ + /* No cache retention priorities */ + put_unaligned_be16(0xffff, &buf[4]); + /* Don't disable prefetch */ + /* Minimum prefetch = 0 */ + put_unaligned_be16(0xffff, &buf[8]); + /* Maximum prefetch */ + put_unaligned_be16(0xffff, &buf[10]); + /* Maximum prefetch ceiling */ + } + buf += 12; + } + + /* Check that a valid page was requested and the mode data length + * isn't too long. */ + len = buf - buf0; + if (!valid_page || len > limit) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + /* Store the mode data length */ + if (mscmnd == SCSI_MODE_SEN6) + buf0[0] = len - 1; + else + put_unaligned_be16(len - 2, buf0); + return len; +} + + +static int do_start_stop(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + + if (!curlun) { + return -EINVAL; + } else if (!curlun->removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + return 0; +} + +static int do_prevent_allow(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + int prevent; + + if (!curlun->removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + prevent = common->cmnd[4] & 0x01; + if ((common->cmnd[4] & ~0x01) != 0) { /* Mask away Prevent */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (curlun->prevent_medium_removal && !prevent) + fsg_lun_fsync_sub(curlun); + curlun->prevent_medium_removal = prevent; + return 0; +} + + +static int do_read_format_capacities(struct fsg_common *common, + struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + u8 *buf = (u8 *) bh->buf; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; /* Only the Current/Maximum Capacity Descriptor */ + buf += 4; + + put_unaligned_be32(curlun->num_sectors, &buf[0]); + /* Number of blocks */ + put_unaligned_be32(512, &buf[4]); /* Block length */ + buf[4] = 0x02; /* Current capacity */ + return 12; +} + + +static int do_mode_select(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + + /* We don't support MODE SELECT */ + if (curlun) + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; +} + + +/*-------------------------------------------------------------------------*/ + +static int halt_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + rc = fsg_set_halt(fsg, fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint halt\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_halt -> %d\n", rc); + rc = 0; + break; + } + + rc = usb_ep_set_halt(fsg->bulk_in); + } + return rc; +} + +static int wedge_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + DBG(fsg, "bulk-in set wedge\n"); + rc = 0; /* usb_ep_set_wedge(fsg->bulk_in); */ + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint wedge\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc); + rc = 0; + break; + } + } + return rc; +} + +static int pad_with_zeros(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh = fsg->common->next_buffhd_to_fill; + u32 nkeep = bh->inreq->length; + u32 nsend; + int rc; + + bh->state = BUF_STATE_EMPTY; /* For the first iteration */ + fsg->common->usb_amount_left = nkeep + fsg->common->residue; + while (fsg->common->usb_amount_left > 0) { + + /* Wait for the next buffer to be free */ + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg->common); + if (rc) + return rc; + } + + nsend = min(fsg->common->usb_amount_left, FSG_BUFLEN); + memset(bh->buf + nkeep, 0, nsend - nkeep); + bh->inreq->length = nsend; + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + bh = fsg->common->next_buffhd_to_fill = bh->next; + fsg->common->usb_amount_left -= nsend; + nkeep = 0; + } + return 0; +} + +static int throw_away_data(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + u32 amount; + int rc; + + for (bh = common->next_buffhd_to_drain; + bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0; + bh = common->next_buffhd_to_drain) { + + /* Throw away the data in a filled buffer */ + if (bh->state == BUF_STATE_FULL) { + bh->state = BUF_STATE_EMPTY; + common->next_buffhd_to_drain = bh->next; + + /* A short packet or an error ends everything */ + if (bh->outreq->actual != bh->outreq->length || + bh->outreq->status != 0) { + raise_exception(common, + FSG_STATE_ABORT_BULK_OUT); + return -EPIPE; + } + continue; + } + + /* Try to submit another request if we need one */ + bh = common->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY + && common->usb_amount_left > 0) { + amount = min(common->usb_amount_left, FSG_BUFLEN); + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + bh->outreq->length = amount; + bh->bulk_out_intended_length = amount; + bh->outreq->short_not_ok = 1; + START_TRANSFER_OR(common, bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state) + /* Don't know what to do if + * common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + common->usb_amount_left -= amount; + continue; + } + + /* Otherwise wait for something to happen */ + rc = sleep_thread(common); + if (rc) + return rc; + } + return 0; +} + + +static int finish_reply(struct fsg_common *common) +{ + struct fsg_buffhd *bh = common->next_buffhd_to_fill; + int rc = 0; + + switch (common->data_dir) { + case DATA_DIR_NONE: + break; /* Nothing to send */ + + /* If we don't know whether the host wants to read or write, + * this must be CB or CBI with an unknown command. We mustn't + * try to send or receive any data. So stall both bulk pipes + * if we can and wait for a reset. */ + case DATA_DIR_UNKNOWN: + if (!common->can_stall) { + /* Nothing */ + } else if (fsg_is_set(common)) { + fsg_set_halt(common->fsg, common->fsg->bulk_out); + rc = halt_bulk_in_endpoint(common->fsg); + } else { + /* Don't know what to do if common->fsg is NULL */ + rc = -EIO; + } + break; + + /* All but the last buffer of data must have already been sent */ + case DATA_DIR_TO_HOST: + if (common->data_size == 0) { + /* Nothing to send */ + + /* If there's no residue, simply send the last buffer */ + } else if (common->residue == 0) { + bh->inreq->zero = 0; + START_TRANSFER_OR(common, bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state) + return -EIO; + common->next_buffhd_to_fill = bh->next; + + /* For Bulk-only, if we're allowed to stall then send the + * short packet and halt the bulk-in endpoint. If we can't + * stall, pad out the remaining data with 0's. */ + } else if (common->can_stall) { + bh->inreq->zero = 1; + START_TRANSFER_OR(common, bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state) + /* Don't know what to do if + * common->fsg is NULL */ + rc = -EIO; + common->next_buffhd_to_fill = bh->next; + if (common->fsg) + rc = halt_bulk_in_endpoint(common->fsg); + } else if (fsg_is_set(common)) { + rc = pad_with_zeros(common->fsg); + } else { + /* Don't know what to do if common->fsg is NULL */ + rc = -EIO; + } + break; + + /* We have processed all we want from the data the host has sent. + * There may still be outstanding bulk-out requests. */ + case DATA_DIR_FROM_HOST: + if (common->residue == 0) { + /* Nothing to receive */ + + /* Did the host stop sending unexpectedly early? */ + } else if (common->short_packet_received) { + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + rc = -EPIPE; + + /* We haven't processed all the incoming data. Even though + * we may be allowed to stall, doing so would cause a race. + * The controller may already have ACK'ed all the remaining + * bulk-out packets, in which case the host wouldn't see a + * STALL. Not realizing the endpoint was halted, it wouldn't + * clear the halt -- leading to problems later on. */ +#if 0 + } else if (common->can_stall) { + if (fsg_is_set(common)) + fsg_set_halt(common->fsg, + common->fsg->bulk_out); + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + rc = -EPIPE; +#endif + + /* We can't stall. Read in the excess data and throw it + * all away. */ + } else { + rc = throw_away_data(common); + } + break; + } + return rc; +} + + +static int send_status(struct fsg_common *common) +{ + struct fsg_lun *curlun = &common->luns[common->lun]; + struct fsg_buffhd *bh; + struct bulk_cs_wrap *csw; + int rc; + u8 status = US_BULK_STAT_OK; + u32 sd, sdinfo = 0; + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + if (curlun) + sd = curlun->sense_data; + else if (common->bad_lun_okay) + sd = SS_NO_SENSE; + else + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + + if (common->phase_error) { + DBG(common, "sending phase-error status\n"); + status = US_BULK_STAT_PHASE; + sd = SS_INVALID_COMMAND; + } else if (sd != SS_NO_SENSE) { + DBG(common, "sending command-failure status\n"); + status = US_BULK_STAT_FAIL; + VDBG(common, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;" + " info x%x\n", + SK(sd), ASC(sd), ASCQ(sd), sdinfo); + } + + /* Store and send the Bulk-only CSW */ + csw = (void *)bh->buf; + + csw->Signature = cpu_to_le32(US_BULK_CS_SIGN); + csw->Tag = common->tag; + csw->Residue = cpu_to_le32(common->residue); + csw->Status = status; + + bh->inreq->length = US_BULK_CS_WRAP_LEN; + bh->inreq->zero = 0; + START_TRANSFER_OR(common, bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + + common->next_buffhd_to_fill = bh->next; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* Check whether the command is properly formed and whether its data size + * and direction agree with the values we already have. */ +static int check_command(struct fsg_common *common, int cmnd_size, + enum data_direction data_dir, unsigned int mask, + int needs_medium, const char *name) +{ + int i; + int lun = common->cmnd[1] >> 5; + static const char dirletter[4] = {'u', 'o', 'i', 'n'}; + char hdlen[20]; + struct fsg_lun *curlun; + + hdlen[0] = 0; + if (common->data_dir != DATA_DIR_UNKNOWN) + sprintf(hdlen, ", H%c=%u", dirletter[(int) common->data_dir], + common->data_size); + VDBG(common, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n", + name, cmnd_size, dirletter[(int) data_dir], + common->data_size_from_cmnd, common->cmnd_size, hdlen); + + /* We can't reply at all until we know the correct data direction + * and size. */ + if (common->data_size_from_cmnd == 0) + data_dir = DATA_DIR_NONE; + if (common->data_size < common->data_size_from_cmnd) { + /* Host data size < Device data size is a phase error. + * Carry out the command, but only transfer as much as + * we are allowed. */ + common->data_size_from_cmnd = common->data_size; + common->phase_error = 1; + } + common->residue = common->data_size; + common->usb_amount_left = common->data_size; + + /* Conflicting data directions is a phase error */ + if (common->data_dir != data_dir + && common->data_size_from_cmnd > 0) { + common->phase_error = 1; + return -EINVAL; + } + + /* Verify the length of the command itself */ + if (cmnd_size != common->cmnd_size) { + + /* Special case workaround: There are plenty of buggy SCSI + * implementations. Many have issues with cbw->Length + * field passing a wrong command size. For those cases we + * always try to work around the problem by using the length + * sent by the host side provided it is at least as large + * as the correct command length. + * Examples of such cases would be MS-Windows, which issues + * REQUEST SENSE with cbw->Length == 12 where it should + * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and + * REQUEST SENSE with cbw->Length == 10 where it should + * be 6 as well. + */ + if (cmnd_size <= common->cmnd_size) { + DBG(common, "%s is buggy! Expected length %d " + "but we got %d\n", name, + cmnd_size, common->cmnd_size); + cmnd_size = common->cmnd_size; + } else { + common->phase_error = 1; + return -EINVAL; + } + } + + /* Check that the LUN values are consistent */ + if (common->lun != lun) + DBG(common, "using LUN %d from CBW, not LUN %d from CDB\n", + common->lun, lun); + + /* Check the LUN */ + if (common->lun < common->nluns) { + curlun = &common->luns[common->lun]; + if (common->cmnd[0] != SCSI_REQ_SENSE) { + curlun->sense_data = SS_NO_SENSE; + curlun->info_valid = 0; + } + } else { + curlun = NULL; + common->bad_lun_okay = 0; + + /* INQUIRY and REQUEST SENSE commands are explicitly allowed + * to use unsupported LUNs; all others may not. */ + if (common->cmnd[0] != SCSI_INQUIRY && + common->cmnd[0] != SCSI_REQ_SENSE) { + DBG(common, "unsupported LUN %d\n", common->lun); + return -EINVAL; + } + } +#if 0 + /* If a unit attention condition exists, only INQUIRY and + * REQUEST SENSE commands are allowed; anything else must fail. */ + if (curlun && curlun->unit_attention_data != SS_NO_SENSE && + common->cmnd[0] != SCSI_INQUIRY && + common->cmnd[0] != SCSI_REQ_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + return -EINVAL; + } +#endif + /* Check that only command bytes listed in the mask are non-zero */ + common->cmnd[1] &= 0x1f; /* Mask away the LUN */ + for (i = 1; i < cmnd_size; ++i) { + if (common->cmnd[i] && !(mask & (1 << i))) { + if (curlun) + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + + return 0; +} + +static int do_scsi_command(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + int rc; + int reply = -EINVAL; + int i; + static char unknown[16]; + struct fsg_lun *curlun = &common->luns[common->lun]; + + dump_cdb(common); + + /* Wait for the next buffer to become available for data or status */ + bh = common->next_buffhd_to_fill; + common->next_buffhd_to_drain = bh; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + common->phase_error = 0; + common->short_packet_received = 0; + + down_read(&common->filesem); /* We're using the backing file */ + switch (common->cmnd[0]) { + + case SCSI_INQUIRY: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "INQUIRY"); + if (reply == 0) + reply = do_inquiry(common, bh); + break; + + case SCSI_MODE_SEL6: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_FROM_HOST, + (1<<1) | (1<<4), 0, + "MODE SELECT(6)"); + if (reply == 0) + reply = do_mode_select(common, bh); + break; + + case SCSI_MODE_SEL10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_FROM_HOST, + (1<<1) | (3<<7), 0, + "MODE SELECT(10)"); + if (reply == 0) + reply = do_mode_select(common, bh); + break; + + case SCSI_MODE_SEN6: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (1<<4), 0, + "MODE SENSE(6)"); + if (reply == 0) + reply = do_mode_sense(common, bh); + break; + + case SCSI_MODE_SEN10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (3<<7), 0, + "MODE SENSE(10)"); + if (reply == 0) + reply = do_mode_sense(common, bh); + break; + + case SCSI_MED_REMOVL: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + (1<<4), 0, + "PREVENT-ALLOW MEDIUM REMOVAL"); + if (reply == 0) + reply = do_prevent_allow(common); + break; + + case SCSI_READ6: + i = common->cmnd[4]; + common->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (7<<1) | (1<<4), 1, + "READ(6)"); + if (reply == 0) + reply = do_read(common); + break; + + case SCSI_READ10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]) << 9; + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "READ(10)"); + if (reply == 0) + reply = do_read(common); + break; + + case SCSI_READ12: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[6]) << 9; + reply = check_command(common, 12, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "READ(12)"); + if (reply == 0) + reply = do_read(common); + break; + + case SCSI_RD_CAPAC: + common->data_size_from_cmnd = 8; + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (0xf<<2) | (1<<8), 1, + "READ CAPACITY"); + if (reply == 0) + reply = do_read_capacity(common, bh); + break; + + case SCSI_RD_HEADER: + if (!common->luns[common->lun].cdrom) + goto unknown_cmnd; + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (3<<7) | (0x1f<<1), 1, + "READ HEADER"); + if (reply == 0) + reply = do_read_header(common, bh); + break; + + case SCSI_RD_TOC: + if (!common->luns[common->lun].cdrom) + goto unknown_cmnd; + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (7<<6) | (1<<1), 1, + "READ TOC"); + if (reply == 0) + reply = do_read_toc(common, bh); + break; + + case SCSI_RD_FMT_CAPAC: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (3<<7), 1, + "READ FORMAT CAPACITIES"); + if (reply == 0) + reply = do_read_format_capacities(common, bh); + break; + + case SCSI_REQ_SENSE: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "REQUEST SENSE"); + if (reply == 0) + reply = do_request_sense(common, bh); + break; + + case SCSI_START_STP: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + (1<<1) | (1<<4), 0, + "START-STOP UNIT"); + if (reply == 0) + reply = do_start_stop(common); + break; + + case SCSI_SYNC_CACHE: + common->data_size_from_cmnd = 0; + reply = check_command(common, 10, DATA_DIR_NONE, + (0xf<<2) | (3<<7), 1, + "SYNCHRONIZE CACHE"); + if (reply == 0) + reply = do_synchronize_cache(common); + break; + + case SCSI_TST_U_RDY: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + 0, 1, + "TEST UNIT READY"); + break; + + /* Although optional, this command is used by MS-Windows. We + * support a minimal version: BytChk must be 0. */ + case SCSI_VERIFY: + common->data_size_from_cmnd = 0; + reply = check_command(common, 10, DATA_DIR_NONE, + (1<<1) | (0xf<<2) | (3<<7), 1, + "VERIFY"); + if (reply == 0) + reply = do_verify(common); + break; + + case SCSI_WRITE6: + i = common->cmnd[4]; + common->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + reply = check_command(common, 6, DATA_DIR_FROM_HOST, + (7<<1) | (1<<4), 1, + "WRITE(6)"); + if (reply == 0) + reply = do_write(common); + break; + + case SCSI_WRITE10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]) << 9; + reply = check_command(common, 10, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "WRITE(10)"); + if (reply == 0) + reply = do_write(common); + break; + + case SCSI_WRITE12: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[6]) << 9; + reply = check_command(common, 12, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "WRITE(12)"); + if (reply == 0) + reply = do_write(common); + break; + + /* Some mandatory commands that we recognize but don't implement. + * They don't mean much in this setting. It's left as an exercise + * for anyone interested to implement RESERVE and RELEASE in terms + * of Posix locks. */ + case SCSI_FORMAT: + case SCSI_RELEASE: + case SCSI_RESERVE: + case SCSI_SEND_DIAG: + /* Fall through */ + + default: +unknown_cmnd: + common->data_size_from_cmnd = 0; + sprintf(unknown, "Unknown x%02x", common->cmnd[0]); + reply = check_command(common, common->cmnd_size, + DATA_DIR_UNKNOWN, 0xff, 0, unknown); + if (reply == 0) { + curlun->sense_data = SS_INVALID_COMMAND; + reply = -EINVAL; + } + break; + } + up_read(&common->filesem); + + if (reply == -EPIPE) + return -EPIPE; + + /* Set up the single reply buffer for finish_reply() */ + if (reply == -EINVAL) + reply = 0; /* Error reply length */ + if (reply >= 0 && common->data_dir == DATA_DIR_TO_HOST) { + reply = min((u32) reply, common->data_size_from_cmnd); + bh->inreq->length = reply; + bh->state = BUF_STATE_FULL; + common->residue -= reply; + } /* Otherwise it's already set */ + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct usb_request *req = bh->outreq; + struct bulk_cb_wrap *cbw = req->buf; + struct fsg_common *common = fsg->common; + + /* Was this a real packet? Should it be ignored? */ + if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags)) + return -EINVAL; + + /* Is the CBW valid? */ + if (req->actual != US_BULK_CB_WRAP_LEN || + cbw->Signature != cpu_to_le32( + US_BULK_CB_SIGN)) { + DBG(fsg, "invalid CBW: len %u sig 0x%x\n", + req->actual, + le32_to_cpu(cbw->Signature)); + + /* The Bulk-only spec says we MUST stall the IN endpoint + * (6.6.1), so it's unavoidable. It also says we must + * retain this state until the next reset, but there's + * no way to tell the controller driver it should ignore + * Clear-Feature(HALT) requests. + * + * We aren't required to halt the OUT endpoint; instead + * we can simply accept and discard any data received + * until the next reset. */ + wedge_bulk_in_endpoint(fsg); + set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + return -EINVAL; + } + + /* Is the CBW meaningful? */ + if (cbw->Lun >= FSG_MAX_LUNS || cbw->Flags & ~US_BULK_FLAG_IN || + cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) { + DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, " + "cmdlen %u\n", + cbw->Lun, cbw->Flags, cbw->Length); + + /* We can do anything we want here, so let's stall the + * bulk pipes if we are allowed to. */ + if (common->can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + halt_bulk_in_endpoint(fsg); + } + return -EINVAL; + } + + /* Save the command for later */ + common->cmnd_size = cbw->Length; + memcpy(common->cmnd, cbw->CDB, common->cmnd_size); + if (cbw->Flags & US_BULK_FLAG_IN) + common->data_dir = DATA_DIR_TO_HOST; + else + common->data_dir = DATA_DIR_FROM_HOST; + common->data_size = le32_to_cpu(cbw->DataTransferLength); + if (common->data_size == 0) + common->data_dir = DATA_DIR_NONE; + common->lun = cbw->Lun; + common->tag = cbw->Tag; + return 0; +} + + +static int get_next_command(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + int rc = 0; + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* Queue a request to read a Bulk-only CBW */ + set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN); + bh->outreq->short_not_ok = 1; + START_TRANSFER_OR(common, bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + + /* We will drain the buffer in software, which means we + * can reuse it for the next filling. No need to advance + * next_buffhd_to_fill. */ + + /* Wait for the CBW to arrive */ + while (bh->state != BUF_STATE_FULL) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO; + bh->state = BUF_STATE_EMPTY; + + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int enable_endpoint(struct fsg_common *common, struct usb_ep *ep) +{ + int rc; + + ep->driver_data = common; + rc = usb_ep_enable(ep); + if (rc) + ERROR(common, "can't enable %s, result %d\n", ep->name, rc); + return rc; +} + +static int alloc_request(struct fsg_common *common, struct usb_ep *ep, + struct usb_request **preq) +{ + *preq = usb_ep_alloc_request(ep); + if (*preq) + return 0; + ERROR(common, "can't allocate request for %s\n", ep->name); + return -ENOMEM; +} + +/* Reset interface setting and re-init endpoint state (toggle etc). */ +static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) +{ + struct fsg_dev *fsg; + int i, rc = 0; + + if (common->running) + DBG(common, "reset interface\n"); + +reset: + /* Deallocate the requests */ + if (common->fsg) { + fsg = common->fsg; + + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &common->buffhds[i]; + + if (bh->inreq) { + usb_ep_free_request(fsg->bulk_in, bh->inreq); + bh->inreq = NULL; + } + if (bh->outreq) { + usb_ep_free_request(fsg->bulk_out, bh->outreq); + bh->outreq = NULL; + } + } + + /* Disable the endpoints */ + if (fsg->bulk_in_enabled) { + usb_ep_disable(fsg->bulk_in); + fsg->bulk_in_enabled = 0; + } + if (fsg->bulk_out_enabled) { + usb_ep_disable(fsg->bulk_out); + fsg->bulk_out_enabled = 0; + } + + common->fsg = NULL; + } + + common->running = 0; + if (!new_fsg || rc) + return rc; + + common->fsg = new_fsg; + fsg = common->fsg; + + /* Enable the endpoints */ + fsg->bulk_in->desc = fsg_ep_desc(common->gadget, + &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc); + rc = enable_endpoint(common, fsg->bulk_in); + if (rc) + goto reset; + fsg->bulk_in_enabled = 1; + + fsg->bulk_out->desc = fsg_ep_desc(common->gadget, + &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc); + rc = enable_endpoint(common, fsg->bulk_out); + if (rc) + goto reset; + fsg->bulk_out_enabled = 1; + common->bulk_out_maxpacket = 512; + clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + + /* Allocate the requests */ + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &common->buffhds[i]; + + rc = alloc_request(common, fsg->bulk_in, &bh->inreq); + if (rc) + goto reset; + rc = alloc_request(common, fsg->bulk_out, &bh->outreq); + if (rc) + goto reset; + bh->inreq->buf = bh->outreq->buf = bh->buf; + bh->inreq->context = bh->outreq->context = bh; + bh->inreq->complete = bulk_in_complete; + bh->outreq->complete = bulk_out_complete; + } + + common->running = 1; + + return rc; +} + + +/****************************** ALT CONFIGS ******************************/ + + +static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct fsg_dev *fsg = fsg_from_func(f); + fsg->common->new_fsg = fsg; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); + return 0; +} + +static void fsg_disable(struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + fsg->common->new_fsg = NULL; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); +} + +/*-------------------------------------------------------------------------*/ + +static void handle_exception(struct fsg_common *common) +{ + int i; + struct fsg_buffhd *bh; + enum fsg_state old_state; + struct fsg_lun *curlun; + unsigned int exception_req_tag; + + /* Cancel all the pending transfers */ + if (common->fsg) { + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &common->buffhds[i]; + if (bh->inreq_busy) + usb_ep_dequeue(common->fsg->bulk_in, bh->inreq); + if (bh->outreq_busy) + usb_ep_dequeue(common->fsg->bulk_out, + bh->outreq); + } + + /* Wait until everything is idle */ + for (;;) { + int num_active = 0; + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &common->buffhds[i]; + num_active += bh->inreq_busy + bh->outreq_busy; + } + if (num_active == 0) + break; + if (sleep_thread(common)) + return; + } + + /* Clear out the controller's fifos */ + if (common->fsg->bulk_in_enabled) + usb_ep_fifo_flush(common->fsg->bulk_in); + if (common->fsg->bulk_out_enabled) + usb_ep_fifo_flush(common->fsg->bulk_out); + } + + /* Reset the I/O buffer states and pointers, the SCSI + * state, and the exception. Then invoke the handler. */ + + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &common->buffhds[i]; + bh->state = BUF_STATE_EMPTY; + } + common->next_buffhd_to_fill = &common->buffhds[0]; + common->next_buffhd_to_drain = &common->buffhds[0]; + exception_req_tag = common->exception_req_tag; + old_state = common->state; + + report_exception("handling", old_state); + + if (old_state == FSG_STATE_ABORT_BULK_OUT) + common->state = FSG_STATE_STATUS_PHASE; + else { + for (i = 0; i < common->nluns; ++i) { + curlun = &common->luns[i]; + curlun->sense_data = SS_NO_SENSE; + curlun->info_valid = 0; + } + common->state = FSG_STATE_IDLE; + } + + /* Carry out any extra actions required for the exception */ + switch (old_state) { + case FSG_STATE_ABORT_BULK_OUT: + send_status(common); + + if (common->state == FSG_STATE_STATUS_PHASE) + common->state = FSG_STATE_IDLE; + break; + + case FSG_STATE_RESET: + /* In case we were forced against our will to halt a + * bulk endpoint, clear the halt now. (The SuperH UDC + * requires this.) */ + if (!fsg_is_set(common)) + break; + if (test_and_clear_bit(IGNORE_BULK_OUT, + &common->fsg->atomic_bitflags)) + usb_ep_clear_halt(common->fsg->bulk_in); + + if (common->ep0_req_tag == exception_req_tag) + ep0_queue(common); /* Complete the status stage */ + + break; + + case FSG_STATE_CONFIG_CHANGE: + do_set_interface(common, common->new_fsg); + break; + + case FSG_STATE_EXIT: + case FSG_STATE_TERMINATED: + do_set_interface(common, NULL); /* Free resources */ + common->state = FSG_STATE_TERMINATED; /* Stop the thread */ + break; + + case FSG_STATE_INTERFACE_CHANGE: + case FSG_STATE_DISCONNECT: + case FSG_STATE_COMMAND_PHASE: + case FSG_STATE_DATA_PHASE: + case FSG_STATE_STATUS_PHASE: + case FSG_STATE_IDLE: + break; + } +} + +/*-------------------------------------------------------------------------*/ + +static void fsg_main_thread(void *fsg_) +{ + struct fsg_dev *fsg = fsg_; + struct fsg_common *common = fsg->common; + struct fsg_buffhd *bh; + unsigned i; + int ret = 0; + + /* The main loop */ + while (common->state != FSG_STATE_TERMINATED) { + if (exception_in_progress(common)) { + handle_exception(common); + continue; + } + + if (!common->running) { + ret = sleep_thread(common); + if (ret) + break; + continue; + } + + ret = get_next_command(common); + if (ret) + continue; + + if (!exception_in_progress(common)) + common->state = FSG_STATE_DATA_PHASE; + + if (do_scsi_command(common) || finish_reply(common)) + continue; + + if (!exception_in_progress(common)) + common->state = FSG_STATE_STATUS_PHASE; + + if (send_status(common)) + continue; + + if (!exception_in_progress(common)) + common->state = FSG_STATE_IDLE; + } + + if (ret && ret != -ERESTARTSYS) + pr_warn("%s: error %pe\n", __func__, ERR_PTR(ret)); + + usb_free_all_descriptors(&fsg->function); + + for (i = 0; i < ums_count; i++) + close(ums[i].fd); + + bh = common->buffhds; + i = FSG_NUM_BUFFERS; + + do { + dma_free(bh->buf); + } while (++bh, --i); + + ums_count = 0; + ums_files = NULL; +} + +static void fsg_common_release(struct fsg_common *common); + +static struct fsg_common *fsg_common_setup(void) +{ + struct fsg_common *common; + + /* Allocate? */ + common = calloc(sizeof(*common), 1); + if (!common) + return NULL; + + common->ops = NULL; + common->private_data = NULL; + + return common; +} + +static int fsg_common_init(struct fsg_common *common, + struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct file_list_entry *fentry; + struct fsg_buffhd *bh; + int nluns, i, fd = -1, rc; + + ums_count = 0; + + common->gadget = gadget; + common->ep0 = gadget->ep0; + common->ep0req = cdev->req; + + thread_task = bthread_run(fsg_main_thread, common->fsg, "mass-storage-gadget"); + if (IS_ERR(thread_task)) + return PTR_ERR(thread_task); + + file_list_detect_all(ums_files); + + file_list_for_each_entry(ums_files, fentry) { + unsigned flags = O_RDWR; + struct stat st; + + if (fentry->flags) { + pr_err("flags not supported\n"); + return -ENOSYS; + } + + fd = open(fentry->filename, flags); + if (fd < 0) { + pr_err("open('%s') failed: %pe\n", + fentry->filename, ERR_PTR(fd)); + return fd; + } + + rc = fstat(fd, &st); + if (rc < 0) { + pr_err("stat('%s') failed: %pe\n", + fentry->filename, ERR_PTR(rc)); + goto close; + } + + if (st.st_size % SECTOR_SIZE != 0) { + pr_err("exporting '%s' failed: invalid block size\n", + fentry->filename); + goto close; + } + + ums[ums_count].fd = fd; + ums[ums_count].num_sectors = st.st_size / SECTOR_SIZE; + + strlcpy(ums[ums_count].name, fentry->name, sizeof(ums[ums_count].name)); + + DBG(common, "LUN %d, %s sector_count %#x\n", + ums_count, fentry->name, ums[ums_count].num_sectors); + + ums_count++; + } + + /* Find out how many LUNs there should be */ + nluns = ums_count; + if (nluns < 1 || nluns > FSG_MAX_LUNS) { + pr_warn("invalid number of LUNs: %u\n", nluns); + rc = -EINVAL; + goto close; + } + + /* Maybe allocate device-global string IDs, and patch descriptors */ + if (fsg_strings[FSG_STRING_INTERFACE].id == 0) { + rc = usb_string_id(cdev); + if (unlikely(rc < 0)) + goto error_release; + fsg_strings[FSG_STRING_INTERFACE].id = rc; + fsg_intf_desc.iInterface = rc; + } + + common->nluns = nluns; + + for (i = 0; i < nluns; i++) { + common->luns[i].removable = 1; + + rc = fsg_lun_open(&common->luns[i], ums[i].num_sectors, ""); + if (rc) + goto error_luns; + } + common->lun = 0; + + /* Data buffers cyclic list */ + bh = common->buffhds; + + i = FSG_NUM_BUFFERS; + goto buffhds_first_it; + do { + bh->next = bh + 1; + ++bh; +buffhds_first_it: + bh->inreq_busy = 0; + bh->outreq_busy = 0; + bh->buf = dma_alloc(FSG_BUFLEN); + if (unlikely(!bh->buf)) { + rc = -ENOMEM; + goto error_release; + } + } while (--i); + bh->next = common->buffhds; + + snprintf(common->inquiry_string, sizeof common->inquiry_string, + "%-8s%-16s%04x", + "Linux ", + "File-Store Gadget", + 0xffff); + + /* Some peripheral controllers are known not to be able to + * halt bulk endpoints correctly. If one of them is present, + * disable stalls. + */ + + /* Information */ + DBG(common, FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); + DBG(common, "Number of LUNs=%d\n", common->nluns); + + return 0; + +error_luns: + common->nluns = i + 1; +error_release: + common->state = FSG_STATE_TERMINATED; /* The thread is dead */ + fsg_common_release(common); +close: + close(fd); + return rc; +} + +static void fsg_common_release(struct fsg_common *common) +{ + /* If the thread isn't already dead, tell it to exit now */ + if (common->state != FSG_STATE_TERMINATED) { + raise_exception(common, FSG_STATE_EXIT); + } + + bthread_cancel(thread_task); +} + + +static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + + DBG(fsg, "unbind\n"); + + if (fsg->common->fsg == fsg) { + fsg->common->new_fsg = NULL; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); + } + + fsg_common_release(fsg->common); +} + +static int fsg_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct usb_gadget *gadget = c->cdev->gadget; + int ret; + struct usb_ep *ep; + struct usb_descriptor_header **hs_function = NULL; + struct fsg_common *common = fsg->common; + + if (!ums_files) { + struct f_ums_opts *opts = container_of(f->fi, struct f_ums_opts, func_inst); + + ums_files = opts->files; + } + + fsg->gadget = gadget; + + DBG(fsg, "bind\n"); + + ret = fsg_common_init(common, c->cdev); + if (ret) + goto remove_ums_files; + + /* New interface */ + ret = usb_interface_id(c, f); + if (ret < 0) + goto fsg_common_release; + fsg_intf_desc.bInterfaceNumber = ret; + fsg->interface_number = ret; + + ret = -EOPNOTSUPP; + + /* Find all the endpoints we will use */ + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc); + if (!ep) + goto autoconf_fail; + + ep->driver_data = common; /* claim the endpoint */ + fsg->bulk_in = ep; + + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc); + if (!ep) + goto autoconf_fail; + + ep->driver_data = common; /* claim the endpoint */ + fsg->bulk_out = ep; + + if (gadget_is_dualspeed(gadget)) { + /* Assume endpoint addresses are the same for both speeds */ + fsg_hs_bulk_in_desc.bEndpointAddress = + fsg_fs_bulk_in_desc.bEndpointAddress; + fsg_hs_bulk_out_desc.bEndpointAddress = + fsg_fs_bulk_out_desc.bEndpointAddress; + hs_function = fsg_hs_function; + } + + /* Copy descriptors */ + return usb_assign_descriptors(f, fsg_fs_function, hs_function, NULL); + +autoconf_fail: + ERROR(fsg, "unable to autoconfigure all endpoints\n"); +fsg_common_release: + fsg_common_release(common); +remove_ums_files: + ums_files = NULL; + + return ret; +} + + +/****************************** ADD FUNCTION ******************************/ + +static struct usb_gadget_strings *fsg_strings_array[] = { + &fsg_stringtab, + NULL, +}; + +static void fsg_free(struct usb_function *f) +{ + struct fsg_dev *fsg; + + fsg = container_of(f, struct fsg_dev, function); + + kfree(fsg); +} + +static struct usb_function *fsg_alloc(struct usb_function_instance *fi) +{ + struct f_ums_opts *opts = fsg_opts_from_func_inst(fi); + struct fsg_common *common = opts->common; + struct fsg_dev *fsg; + + fsg = kzalloc(sizeof(*fsg), GFP_KERNEL); + if (!fsg) + return ERR_PTR(-ENOMEM); + + fsg->function.name = FSG_DRIVER_DESC; + fsg->function.strings = fsg_strings_array; + /* descriptors are per-instance copies */ + fsg->function.bind = fsg_bind; + fsg->function.set_alt = fsg_set_alt; + fsg->function.setup = fsg_setup; + fsg->function.disable = fsg_disable; + fsg->function.unbind = fsg_unbind; + fsg->function.free_func = fsg_free; + + fsg->common = common; + common->fsg = fsg; + + return &fsg->function; +} + +static void fsg_free_instance(struct usb_function_instance *fi) +{ + struct f_ums_opts *opts = fsg_opts_from_func_inst(fi); + + kfree(opts->common); + kfree(opts); +} + +static struct usb_function_instance *fsg_alloc_inst(void) +{ + struct f_ums_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.free_func_inst = fsg_free_instance; + + opts->common = fsg_common_setup(); + if (!opts->common) { + free(opts); + return ERR_PTR(-ENOMEM); + } + + return &opts->func_inst; +} + +DECLARE_USB_FUNCTION_INIT(ums, fsg_alloc_inst, fsg_alloc); diff --git a/drivers/usb/gadget/multi.c b/drivers/usb/gadget/multi.c index 144ac0624b..04b3c2604e 100644 --- a/drivers/usb/gadget/multi.c +++ b/drivers/usb/gadget/multi.c @@ -60,6 +60,8 @@ static struct usb_function_instance *fi_dfu; static struct usb_function *f_dfu; static struct usb_function_instance *fi_fastboot; static struct usb_function *f_fastboot; +static struct usb_function_instance *fi_ums; +static struct usb_function *f_ums; static struct usb_configuration config = { .bConfigurationValue = 1, @@ -139,6 +141,31 @@ static int multi_bind_fastboot(struct usb_composite_dev *cdev) return usb_add_function(&config, f_fastboot); } +static int multi_bind_ums(struct usb_composite_dev *cdev) +{ + int ret; + struct f_ums_opts *opts; + + fi_ums = usb_get_function_instance("ums"); + if (IS_ERR(fi_ums)) { + ret = PTR_ERR(fi_ums); + fi_ums = NULL; + return ret; + } + + opts = container_of(fi_ums, struct f_ums_opts, func_inst); + opts->files = gadget_multi_opts->ums_opts.files; + + f_ums = usb_get_function(fi_ums); + if (IS_ERR(f_ums)) { + ret = PTR_ERR(f_ums); + f_ums = NULL; + return ret; + } + + return usb_add_function(&config, f_ums); +} + static int multi_unbind(struct usb_composite_dev *cdev) { if (gadget_multi_opts->create_acm) { @@ -205,6 +232,13 @@ static int multi_bind(struct usb_composite_dev *cdev) goto out; } + if (gadget_multi_opts->ums_opts.files) { + printf("%s: creating USB Mass Storage function\n", __func__); + ret = multi_bind_ums(cdev); + if (ret) + goto out; + } + if (gadget_multi_opts->create_acm) { printf("%s: creating ACM function\n", __func__); ret = multi_bind_acm(cdev); @@ -273,6 +307,7 @@ unsigned usb_multi_count_functions(struct f_multi_opts *opts) count += !file_list_empty(opts->fastboot_opts.files) || opts->fastboot_opts.export_bbu; count += !file_list_empty(opts->dfu_opts.files); + count += !file_list_empty(opts->ums_opts.files); count += opts->create_acm; return count; @@ -282,6 +317,7 @@ void usb_multi_opts_release(struct f_multi_opts *opts) { file_list_free(opts->fastboot_opts.files); file_list_free(opts->dfu_opts.files); + file_list_free(opts->ums_opts.files); free(opts); } diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c new file mode 100644 index 0000000000..88cd745063 --- /dev/null +++ b/drivers/usb/gadget/storage_common.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * storage_common.c -- Common definitions for mass storage functionality + * + * Copyright (C) 2003-2008 Alan Stern + * Copyeight (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz (m.nazarewicz@samsung.com) + * + * Ported to u-boot: + * Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * Code refactoring & cleanup: + * Ćukasz Majewski <l.majewski@samsung.com> + */ + +#include "storage_common.h" + +/* + * This file requires the following identifiers used in USB strings to + * be defined (each of type pointer to char): + * - fsg_string_manufacturer -- name of the manufacturer + * - fsg_string_product -- name of the product + * - fsg_string_serial -- product's serial + * - fsg_string_config -- name of the configuration + * - fsg_string_interface -- name of the interface + * The first four are only needed when FSG_DESCRIPTORS_DEVICE_STRINGS + * macro is defined prior to including this file. + */ + +/* There is only one interface. */ + +struct usb_interface_descriptor fsg_intf_desc = { + .bLength = sizeof fsg_intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bNumEndpoints = 2, /* Adjusted during fsg_bind() */ + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, /* Adjusted during fsg_bind() */ + .bInterfaceProtocol = USB_PR_BULK, /* Adjusted during fsg_bind() */ + .iInterface = FSG_STRING_INTERFACE, +}; + +/* + * Three full-speed endpoint descriptors: bulk-in, bulk-out, and + * interrupt-in. + */ + +struct usb_endpoint_descriptor fsg_fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +struct usb_endpoint_descriptor fsg_fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +struct usb_descriptor_header *fsg_fs_function[] = { + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_fs_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_fs_bulk_out_desc, + NULL, +}; + +/* + * USB 2.0 devices need to expose both high speed and full speed + * descriptors, unless they only run at full speed. + * + * That means alternate endpoint descriptors (bigger packets) + * and a "device qualifier" ... plus more construction options + * for the configuration descriptor. + */ +struct usb_endpoint_descriptor fsg_hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +struct usb_endpoint_descriptor fsg_hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), + .bInterval = 1, /* NAK every 1 uframe */ +}; + +struct usb_descriptor_header *fsg_hs_function[] = { + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_hs_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_hs_bulk_out_desc, + NULL, +}; + +/* Maxpacket and other transfer characteristics vary by speed. */ +struct usb_endpoint_descriptor * +fsg_ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs, + struct usb_endpoint_descriptor *hs) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return hs; + return fs; +} + +/*-------------------------------------------------------------------------*/ + +/* + * If the next two routines are called while the gadget is registered, + * the caller must own fsg->filesem for writing. + */ + +int fsg_lun_open(struct fsg_lun *curlun, unsigned int num_sectors, + const char *filename) +{ + int ro; + + /* R/W if we can, R/O if we must */ + ro = curlun->initially_ro; + + curlun->ro = ro; + curlun->file_length = num_sectors << 9; + curlun->num_sectors = num_sectors; + debug("open backing file: %s\n", filename); + + return 0; +} + +void fsg_lun_close(struct fsg_lun *curlun) +{ +} + +/*-------------------------------------------------------------------------*/ + +/* + * Sync the file data, don't bother with the metadata. + * This code was copied from fs/buffer.c:sys_fdatasync(). + */ +int fsg_lun_fsync_sub(struct fsg_lun *curlun) +{ + return 0; +} + +void store_cdrom_address(u8 *dest, int msf, u32 addr) +{ + if (msf) { + /* Convert to Minutes-Seconds-Frames */ + addr >>= 2; /* Convert to 2048-byte frames */ + addr += 2*75; /* Lead-in occupies 2 seconds */ + dest[3] = addr % 75; /* Frames */ + addr /= 75; + dest[2] = addr % 60; /* Seconds */ + addr /= 60; + dest[1] = addr; /* Minutes */ + dest[0] = 0; /* Reserved */ + } else { + /* Absolute sector */ + put_unaligned_be32(addr, dest); + } +} + +/*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/gadget/storage_common.h b/drivers/usb/gadget/storage_common.h new file mode 100644 index 0000000000..ce07a7dac7 --- /dev/null +++ b/drivers/usb/gadget/storage_common.h @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef USB_STORAGE_COMMON_H +#define USB_STORAGE_COMMON_H + +#include <driver.h> +#include <usb/storage.h> +#include <asm/unaligned.h> +#include <usb/mass_storage.h> + +#ifndef DEBUG +#undef VERBOSE_DEBUG +#undef DUMP_MSGS +#endif /* !DEBUG */ + +#define VLDBG(lun, fmt, args...) dev_vdbg(&(lun)->dev, fmt, ## args) +#define LDBG(lun, fmt, args...) dev_dbg (&(lun)->dev, fmt, ## args) +#define LERROR(lun, fmt, args...) dev_err (&(lun)->dev, fmt, ## args) +#define LWARN(lun, fmt, args...) dev_warn(&(lun)->dev, fmt, ## args) +#define LINFO(lun, fmt, args...) dev_info(&(lun)->dev, fmt, ## args) + +/* + * Keep those macros in sync with those in + * include/linux/usb/composite.h or else GCC will complain. If they + * are identical (the same names of arguments, white spaces in the + * same places) GCC will allow redefinition otherwise (even if some + * white space is removed or added) warning will be issued. + * + * Those macros are needed here because File Storage Gadget does not + * include the composite.h header. For composite gadgets those macros + * are redundant since composite.h is included any way. + * + * One could check whether those macros are already defined (which + * would indicate composite.h had been included) or not (which would + * indicate we were in FSG) but this is not done because a warning is + * desired if definitions here differ from the ones in composite.h. + * + * We want the definitions to match and be the same in File Storage + * Gadget as well as Mass Storage Function (and so composite gadgets + * using MSF). If someone changes them in composite.h it will produce + * a warning in this file when building MSF. + */ + +#define DBG(d, fmt, args...) \ + dev_dbg(&(d)->gadget->dev , fmt , ## args) +#define VDBG(d, fmt, args...) \ + dev_vdbg(&(d)->gadget->dev , fmt , ## args) +#define ERROR(d, fmt, args...) \ + dev_err(&(d)->gadget->dev , fmt , ## args) +#define WARNING(d, fmt, args...) \ + dev_warn(&(d)->gadget->dev , fmt , ## args) +#define INFO(d, fmt, args...) \ + dev_info(&(d)->gadget->dev , fmt , ## args) + +#ifdef DUMP_MSGS + +/* dump_msg(fsg, const char * label, const u8 * buf, unsigned length); */ +# define dump_msg(fsg, label, buf, length) do { \ + if (length < 512) { \ + DBG(fsg, "%s, length %u:\n", label, length); \ + print_hex_dump("", DUMP_PREFIX_OFFSET, \ + 16, 1, buf, length, 0); \ + } \ +} while (0) + +# define dump_cdb(fsg) do { } while (0) + +#else + +# define dump_msg(fsg, /* const char * */ label, \ + /* const u8 * */ buf, /* unsigned */ length) do { } while (0) + +# ifdef VERBOSE_DEBUG + +# define dump_cdb(fsg) \ + print_hex_dump("SCSI CDB: ", DUMP_PREFIX_NONE, \ + 16, 1, (fsg)->cmnd, (fsg)->cmnd_size, 0) \ + +# else + +# define dump_cdb(fsg) do { } while (0) + +# endif /* VERBOSE_DEBUG */ + +#endif /* DUMP_MSGS */ + +/* + * Thanks to NetChip Technologies for donating this product ID. + * + * DO NOT REUSE THESE IDs with any other driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +#define FSG_VENDOR_ID 0x0525 /* NetChip */ +#define FSG_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ + +/* Length of a SCSI Command Data Block */ +#define MAX_COMMAND_SIZE 16 + +/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ +#define SS_NO_SENSE 0 +#define SS_COMMUNICATION_FAILURE 0x040800 +#define SS_INVALID_COMMAND 0x052000 +#define SS_INVALID_FIELD_IN_CDB 0x052400 +#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100 +#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500 +#define SS_MEDIUM_NOT_PRESENT 0x023a00 +#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302 +#define SS_NOT_READY_TO_READY_TRANSITION 0x062800 +#define SS_RESET_OCCURRED 0x062900 +#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900 +#define SS_UNRECOVERED_READ_ERROR 0x031100 +#define SS_WRITE_ERROR 0x030c02 +#define SS_WRITE_PROTECTED 0x072700 + +#define SK(x) ((u8) ((x) >> 16)) /* Sense Key byte, etc. */ +#define ASC(x) ((u8) ((x) >> 8)) +#define ASCQ(x) ((u8) (x)) + +/*-------------------------------------------------------------------------*/ + +struct fsg_lun { + loff_t file_length; + loff_t num_sectors; + + unsigned int initially_ro:1; + unsigned int ro:1; + unsigned int removable:1; + unsigned int cdrom:1; + unsigned int prevent_medium_removal:1; + unsigned int registered:1; + unsigned int info_valid:1; + unsigned int nofua:1; + + u32 sense_data; + u32 sense_data_info; + u32 unit_attention_data; + + struct device_d dev; +}; + +#define fsg_lun_is_open(curlun) ((curlun)->filp != NULL) + +/* Big enough to hold our biggest descriptor */ +#define EP0_BUFSIZE 256 +#define DELAYED_STATUS (EP0_BUFSIZE + 999) /* An impossibly large value */ + +/* Number of buffers we will use. 2 is enough for double-buffering */ +#define FSG_NUM_BUFFERS 2 + +/* Default size of buffer length. */ +#define FSG_BUFLEN ((u32)131072) + +/* Maximal number of LUNs supported in mass storage function */ +#define FSG_MAX_LUNS 8 + +enum fsg_buffer_state { + BUF_STATE_EMPTY = 0, + BUF_STATE_FULL, + BUF_STATE_BUSY +}; + +/* + * When FSG_BUFFHD_STATIC_BUFFER is defined when this file is included + * the fsg_buffhd structure's buf field will be an array of FSG_BUFLEN + * characters rather then a pointer to void. + */ + +struct fsg_buffhd { + void *buf; + enum fsg_buffer_state state; + struct fsg_buffhd *next; + + /* + * The NetChip 2280 is faster, and handles some protocol faults + * better, if we don't submit any short bulk-out read requests. + * So we will record the intended request length here. + */ + unsigned int bulk_out_intended_length; + + struct usb_request *inreq; + int inreq_busy; + struct usb_request *outreq; + int outreq_busy; +}; + +enum fsg_state { + /* This one isn't used anywhere */ + FSG_STATE_COMMAND_PHASE = -10, + FSG_STATE_DATA_PHASE, + FSG_STATE_STATUS_PHASE, + + FSG_STATE_IDLE = 0, + FSG_STATE_ABORT_BULK_OUT, + FSG_STATE_RESET, + FSG_STATE_INTERFACE_CHANGE, + FSG_STATE_CONFIG_CHANGE, + FSG_STATE_DISCONNECT, + FSG_STATE_EXIT, + FSG_STATE_TERMINATED +}; + +enum data_direction { + DATA_DIR_UNKNOWN = 0, + DATA_DIR_FROM_HOST, + DATA_DIR_TO_HOST, + DATA_DIR_NONE +}; + +/*-------------------------------------------------------------------------*/ + +static inline u32 get_unaligned_be24(u8 *buf) +{ + return 0xffffff & (u32) get_unaligned_be32(buf - 1); +} + +/*-------------------------------------------------------------------------*/ + +enum { + FSG_STRING_INTERFACE +}; + +/*-------------------------------------------------------------------------*/ + +extern struct usb_interface_descriptor fsg_intf_desc; + +extern struct usb_endpoint_descriptor fsg_fs_bulk_in_desc; +extern struct usb_endpoint_descriptor fsg_fs_bulk_out_desc; +extern struct usb_descriptor_header *fsg_fs_function[]; + +extern struct usb_endpoint_descriptor fsg_hs_bulk_in_desc; +extern struct usb_endpoint_descriptor fsg_hs_bulk_out_desc; +extern struct usb_descriptor_header *fsg_hs_function[]; + +int fsg_lun_open(struct fsg_lun *curlun, unsigned int num_sectors, + const char *filename); +void fsg_lun_close(struct fsg_lun *curlun); + +struct usb_endpoint_descriptor * +fsg_ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs, + struct usb_endpoint_descriptor *hs); +int fsg_lun_fsync_sub(struct fsg_lun *curlun); +void store_cdrom_address(u8 *dest, int msf, u32 addr); + +#endif /* USB_STORAGE_COMMON_H */ diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 8c4da9fd12..30a16103eb 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -32,6 +32,8 @@ #include <usb/ehci.h> #include <linux/err.h> #include <linux/sizes.h> +#include <linux/clk.h> +#include <linux/phy/phy.h> #include "ehci.h" @@ -305,7 +307,7 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer, struct usb_host *host = dev->host; struct ehci_host *ehci = to_ehci(host); const bool dir_in = usb_pipein(pipe); - dma_addr_t buffer_dma, req_dma; + dma_addr_t buffer_dma = DMA_ERROR_CODE, req_dma; struct QH *qh = &ehci->qh_list[1]; struct qTD *td; volatile struct qTD *vtd; @@ -1413,6 +1415,9 @@ static int ehci_probe(struct device_d *dev) struct ehci_platform_data *pdata = dev->platform_data; struct device_node *dn = dev->device_node; struct ehci_host *ehci; + struct clk_bulk_data *clks; + int num_clocks, ret; + struct phy *usb2_generic_phy; if (pdata) data.flags = pdata->flags; @@ -1440,6 +1445,27 @@ static int ehci_probe(struct device_d *dev) else data.hcor = NULL; + usb2_generic_phy = phy_optional_get(dev, "usb"); + if (IS_ERR(usb2_generic_phy)) + return PTR_ERR(usb2_generic_phy); + + ret = phy_init(usb2_generic_phy); + if (ret) + return ret; + + ret = phy_power_on(usb2_generic_phy); + if (ret) + return ret; + + ret = clk_bulk_get_all(dev, &clks); + if (ret < 0) + return ret; + + num_clocks = ret; + ret = clk_bulk_enable(num_clocks, clks); + if (ret) + return ret; + ehci = ehci_register(dev, &data); if (IS_ERR(ehci)) return PTR_ERR(ehci); diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c index 10d5aa310b..e282988fb4 100644 --- a/drivers/usb/misc/usb251xb.c +++ b/drivers/usb/misc/usb251xb.c @@ -560,7 +560,7 @@ static int usb251xb_get_ofdata(struct usb251xb *hub, */ hub->port_swap = USB251XB_DEF_PORT_SWAP; of_property_for_each_u32(np, "swap-dx-lanes", prop, p, port) { - if ((port >= 0) && (port <= data->port_cnt)) + if (port <= data->port_cnt) hub->port_swap |= BIT(port); } diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index b0f789b42a..a43b498e4b 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -420,7 +420,7 @@ static int usb_stor_add_blkdev(struct us_data *us, unsigned char lun) result = cdev_find_free_index("disk"); if (result == -1) pr_err("Cannot find a free number for the disk node\n"); - pr_info("Using index %d for the new disk\n", result); + dev_info(dev, "registering as disk%d\n", result); pblk_dev->blk.cdev.name = basprintf("disk%d", result); pblk_dev->blk.blockbits = SECTOR_SHIFT; diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index 916027ea87..8f2f772c6e 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -1,13 +1,8 @@ -/* - * w1-gpio - GPIO w1 bus master driver - * - * Copyright (C) 2007 Ville Syrjala <syrjala@sci.fi> - * Copyright (c) 2012 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> - * - * 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. - */ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2007 Ville Syrjala <syrjala@sci.fi> +// SPDX-FileCopyrightText: 2012 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> + +/* w1-gpio - GPIO w1 bus master driver */ #include <common.h> #include <init.h> diff --git a/drivers/w1/slaves/w1_ds2431.c b/drivers/w1/slaves/w1_ds2431.c index ab2ceffa42..9a5f6b5dae 100644 --- a/drivers/w1/slaves/w1_ds2431.c +++ b/drivers/w1/slaves/w1_ds2431.c @@ -1,13 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2008 Bernhard Weirich <bernhard.weirich@riedel.net> +// SPDX-FileCopyrightText: 2012 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> + /* * w1_ds2431.c - w1 family 2d (DS2431) driver * - * Copyright (c) 2008 Bernhard Weirich <bernhard.weirich@riedel.net> - * Copyright (c) 2012 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> - * * Heavily inspired by w1_DS2433 driver from Ben Gardner <bgardner@wabtec.com> - * - * This source code is licensed under the GNU General Public License, - * Version 2. See the file COPYING for more details. */ #include <init.h> diff --git a/drivers/w1/slaves/w1_ds2433.c b/drivers/w1/slaves/w1_ds2433.c index b24fb5b3b5..0e626530db 100644 --- a/drivers/w1/slaves/w1_ds2433.c +++ b/drivers/w1/slaves/w1_ds2433.c @@ -1,11 +1,7 @@ -/* - * w1_ds2433.c - w1 family 23 (DS2433) driver - * - * Copyright (c) 2005 Ben Gardner <bgardner@wabtec.com> - * - * This source code is licensed under the GNU General Public License, - * Version 2. See the file COPYING for more details. - */ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2005 Ben Gardner <bgardner@wabtec.com> + +/* w1_ds2433.c - w1 family 23 (DS2433) driver */ #include <init.h> #include "../w1.h" diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b785181c59..080bc91ff7 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -141,4 +141,11 @@ config ITCO_WDT NO_REBOOT flag which prevents the watchdog from rebooting the machine. +config STARFIVE_WDT + tristate "StarFive Watchdog Timer" + depends on SOC_STARFIVE && OFDEVICE + help + If you say yes here you get support for the watchdog device + on StarFive SoCs. + endif diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 0b598af402..4e784b5aaa 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o obj-$(CONFIG_ITCO_WDT) += itco_wdt.o +obj-$(CONFIG_STARFIVE_WDT) += starfive_wdt.o diff --git a/drivers/watchdog/starfive_wdt.c b/drivers/watchdog/starfive_wdt.c new file mode 100644 index 0000000000..6779566fd6 --- /dev/null +++ b/drivers/watchdog/starfive_wdt.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Kalray Inc. + */ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <of.h> +#include <watchdog.h> +#include <linux/reset.h> + +#include <linux/clk.h> +#include <linux/err.h> + +#define WDT_REG_RESET_EN 0x104 +#define WDT_REG_TIMEOUT 0x108 +#define WDT_REG_CONTROL 0x110 +#define WDT_REG_UNLOCK 0x13c + +#define WDT_UNLOCK_KEY 0x378f0765 + +#define WDT_TICKS_PER_SEC 50000000 + +struct starfive_wdt { + u32 clk_rate; + struct watchdog wdd; + void __iomem *base; + bool setup; +}; + +static int starfive_wdt_set_timeout(struct watchdog *wdd, unsigned int timeout) +{ + struct starfive_wdt *wd = container_of(wdd, struct starfive_wdt, wdd); + + writel(0, wd->base + WDT_REG_CONTROL); + + if (timeout > 0) { + timeout *= wd->clk_rate; + writel(timeout, wd->base + WDT_REG_TIMEOUT); + writel(1, wd->base + WDT_REG_CONTROL); + } + + return 0; +} + +static int starfive_wdt_drv_probe(struct device_d *dev) +{ + struct starfive_wdt *wd; + struct resource *iores; + struct watchdog *wdd; + struct clk_bulk_data clks[] = { + { .id = "bus" }, + { .id = "core" }, + }; + int ret; + + ret = clk_bulk_get(dev, ARRAY_SIZE(clks), clks); + if (ret) + return ret; + + ret = clk_bulk_enable(ARRAY_SIZE(clks), clks); + if (ret < 0) + return ret; + + ret = device_reset_all(dev); + if (ret) + return ret; + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + + wd = xzalloc(sizeof(*wd)); + wd->base = IOMEM(iores->start); + + wd->clk_rate = WDT_TICKS_PER_SEC; + + writel(WDT_UNLOCK_KEY, wd->base + WDT_REG_UNLOCK); + wd->base = IOMEM(iores->start); + /* reset, not interrupt, on timer expiry */ + writel(1, wd->base + WDT_REG_RESET_EN); + + wdd = &wd->wdd; + wdd->name = "starfive_wdt"; + wdd->hwdev = dev; + wdd->set_timeout = starfive_wdt_set_timeout; + wdd->timeout_max = U32_MAX / wd->clk_rate; + + wdd->running = readl(wd->base + WDT_REG_CONTROL) & 1 ? + WDOG_HW_RUNNING : WDOG_HW_NOT_RUNNING; + + return watchdog_register(wdd); +} + +static struct of_device_id starfive_wdt_of_match[] = { + { .compatible = "starfive,wdt", }, + { /* sentinel */ } +}; + +static struct driver_d starfive_wdt_driver = { + .name = "starfive-wdt", + .probe = starfive_wdt_drv_probe, + .of_compatible = starfive_wdt_of_match, +}; +device_platform_driver(starfive_wdt_driver); |