summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/Kconfig1
-rw-r--r--drivers/Makefile3
-rw-r--r--drivers/aiodev/Kconfig8
-rw-r--r--drivers/aiodev/Makefile1
-rw-r--r--drivers/aiodev/rockchip_saradc.c197
-rw-r--r--drivers/base/driver.c28
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/clk.c29
-rw-r--r--drivers/clk/rockchip/clk-rk3568.c34
-rw-r--r--drivers/clk/starfive/Makefile3
-rw-r--r--drivers/clk/starfive/clk.h64
-rw-r--r--drivers/clk/starfive/jh7100-clkgen.c363
-rw-r--r--drivers/clocksource/timer-clint.c7
-rw-r--r--drivers/clocksource/timer-riscv.c5
-rw-r--r--drivers/firmware/Makefile2
-rw-r--r--drivers/firmware/altera_serial.c1
-rw-r--r--drivers/firmware/socfpga.c97
-rw-r--r--drivers/firmware/socfpga_sdr.S20
-rw-r--r--drivers/firmware/zynqmp-fpga.c2
-rw-r--r--drivers/fpga/Kconfig30
-rw-r--r--drivers/fpga/Makefile7
-rw-r--r--drivers/fpga/fpga-bridge.c243
-rw-r--r--drivers/fpga/socfpga-fpga2sdram-bridge.c139
-rw-r--r--drivers/fpga/socfpga-hps2fpga-bridge.c179
-rw-r--r--drivers/gpio/Kconfig8
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-starfive-vic.c177
-rw-r--r--drivers/hab/habv4.c3
-rw-r--r--drivers/hw_random/Kconfig7
-rw-r--r--drivers/hw_random/Makefile1
-rw-r--r--drivers/hw_random/starfive-vic-rng.c208
-rw-r--r--drivers/i2c/i2c.c8
-rw-r--r--drivers/input/Kconfig3
-rw-r--r--drivers/input/virtio_input.c45
-rw-r--r--drivers/mci/dw_mmc.c23
-rw-r--r--drivers/mci/mci-core.c15
-rw-r--r--drivers/misc/Kconfig10
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/starfive-pwrseq.c92
-rw-r--r--drivers/mtd/nand/nand_base.c1
-rw-r--r--drivers/net/Kconfig8
-rw-r--r--drivers/net/Makefile1
-rw-r--r--drivers/net/designware.c64
-rw-r--r--drivers/net/designware.h29
-rw-r--r--drivers/net/designware_starfive.c110
-rw-r--r--drivers/nvmem/Kconfig14
-rw-r--r--drivers/nvmem/Makefile6
-rw-r--r--drivers/nvmem/bsec.c1
-rw-r--r--drivers/nvmem/core.c27
-rw-r--r--drivers/nvmem/partition.c40
-rw-r--r--drivers/nvmem/rmem.c67
-rw-r--r--drivers/nvmem/starfive-otp.c201
-rw-r--r--drivers/of/Kconfig1
-rw-r--r--drivers/of/base.c114
-rw-r--r--drivers/of/fdt.c17
-rw-r--r--drivers/of/of_firmware.c137
-rw-r--r--drivers/of/of_net.c78
-rw-r--r--drivers/of/overlay.c355
-rw-r--r--drivers/of/partition.c7
-rw-r--r--drivers/of/platform.c240
-rw-r--r--drivers/phy/Kconfig1
-rw-r--r--drivers/phy/Makefile1
-rw-r--r--drivers/phy/phy-core.c4
-rw-r--r--drivers/phy/rockchip/Kconfig14
-rw-r--r--drivers/phy/rockchip/Makefile2
-rw-r--r--drivers/phy/rockchip/phy-rockchip-inno-usb2.c981
-rw-r--r--drivers/phy/rockchip/phy-rockchip-naneng-combphy.c607
-rw-r--r--drivers/pinctrl/pinctrl-rockchip.c104
-rw-r--r--drivers/pinctrl/pinctrl-single.c4
-rw-r--r--drivers/pinctrl/pinctrl-stm32.c5
-rw-r--r--drivers/pinctrl/pinctrl.c16
-rw-r--r--drivers/power/reset/Kconfig10
-rw-r--r--drivers/power/reset/Makefile1
-rw-r--r--drivers/power/reset/nvmem-reboot-mode.c83
-rw-r--r--drivers/regulator/core.c6
-rw-r--r--drivers/regulator/fixed.c12
-rw-r--r--drivers/reset/Kconfig6
-rw-r--r--drivers/reset/Makefile1
-rw-r--r--drivers/reset/core.c82
-rw-r--r--drivers/reset/reset-starfive-vic.c234
-rw-r--r--drivers/reset/reset-stm32.c4
-rw-r--r--drivers/soc/Makefile5
-rw-r--r--drivers/soc/sifive/Makefile1
-rw-r--r--drivers/soc/sifive/sifive_l2_cache.c136
-rw-r--r--drivers/soc/starfive/Makefile1
-rw-r--r--drivers/soc/starfive/jh7100_dma.c55
-rw-r--r--drivers/spi/Kconfig2
-rw-r--r--drivers/spi/spi.c2
-rw-r--r--drivers/usb/dwc3/gadget.c8
-rw-r--r--drivers/usb/gadget/Kconfig15
-rw-r--r--drivers/usb/gadget/Makefile1
-rw-r--r--drivers/usb/gadget/f_mass_storage.c2714
-rw-r--r--drivers/usb/gadget/multi.c36
-rw-r--r--drivers/usb/gadget/storage_common.c173
-rw-r--r--drivers/usb/gadget/storage_common.h245
-rw-r--r--drivers/usb/host/ehci-hcd.c28
-rw-r--r--drivers/usb/misc/usb251xb.c2
-rw-r--r--drivers/usb/storage/usb.c2
-rw-r--r--drivers/w1/masters/w1-gpio.c15
-rw-r--r--drivers/w1/slaves/w1_ds2431.c10
-rw-r--r--drivers/w1/slaves/w1_ds2433.c12
-rw-r--r--drivers/watchdog/Kconfig7
-rw-r--r--drivers/watchdog/Makefile1
-rw-r--r--drivers/watchdog/starfive_wdt.c106
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", &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, &regulator_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);