diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/clk/clk.c | 49 | ||||
-rw-r--r-- | drivers/mci/Kconfig | 4 | ||||
-rw-r--r-- | drivers/mci/Makefile | 1 | ||||
-rw-r--r-- | drivers/mci/atmel-sdhci-common.c | 58 | ||||
-rw-r--r-- | drivers/mci/atmel-sdhci-pbl.c | 128 | ||||
-rw-r--r-- | drivers/mci/atmel-sdhci.c | 4 | ||||
-rw-r--r-- | drivers/mci/atmel-sdhci.h | 1 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 6 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/at91sam9_wdt.c | 109 |
10 files changed, 333 insertions, 28 deletions
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 4386c95aa9..f2e459a760 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -469,21 +469,60 @@ unsigned int of_clk_get_parent_count(struct device_node *np) } EXPORT_SYMBOL_GPL(of_clk_get_parent_count); -char *of_clk_get_parent_name(struct device_node *np, unsigned int index) +char *of_clk_get_parent_name(const struct device_node *np, int index) { struct of_phandle_args clkspec; + struct property *prop; const char *clk_name; + const __be32 *vp; + u32 pv; int rc; + int count; + struct clk *clk; rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index, - &clkspec); + &clkspec); if (rc) return NULL; + index = clkspec.args_count ? clkspec.args[0] : 0; + count = 0; + + /* if there is an indices property, use it to transfer the index + * specified into an array offset for the clock-output-names property. + */ + of_property_for_each_u32(clkspec.np, "clock-indices", prop, vp, pv) { + if (index == pv) { + index = count; + break; + } + count++; + } + /* We went off the end of 'clock-indices' without finding it */ + if (prop && !vp) + return NULL; + if (of_property_read_string_index(clkspec.np, "clock-output-names", - clkspec.args_count ? clkspec.args[0] : 0, - &clk_name) < 0) - clk_name = clkspec.np->name; + index, + &clk_name) < 0) { + /* + * Best effort to get the name if the clock has been + * registered with the framework. If the clock isn't + * registered, we return the node name as the name of + * the clock as long as #clock-cells = 0. + */ + clk = of_clk_get_from_provider(&clkspec); + if (IS_ERR(clk)) { + if (clkspec.args_count == 0) + clk_name = clkspec.np->name; + else + clk_name = NULL; + } else { + clk_name = __clk_get_name(clk); + clk_put(clk); + } + } + return xstrdup(clk_name); } diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig index 6ae1e81252..3026b25cad 100644 --- a/drivers/mci/Kconfig +++ b/drivers/mci/Kconfig @@ -187,3 +187,7 @@ endif config MCI_IMX_ESDHC_PBL bool select MCI_SDHCI + +config MCI_ATMEL_SDHCI_PBL + bool + select MCI_SDHCI diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile index 177483dcfb..4a53633674 100644 --- a/drivers/mci/Makefile +++ b/drivers/mci/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_MCI_ATMEL_SDHCI) += atmel-sdhci.o atmel-sdhci-common.o obj-$(CONFIG_MCI_BCM283X) += mci-bcm2835.o obj-$(CONFIG_MCI_BCM283X_SDHOST) += bcm2835-sdhost.o obj-$(CONFIG_MCI_DOVE) += dove-sdhci.o +pbl-$(CONFIG_MCI_ATMEL_SDHCI_PBL) += atmel-sdhci-pbl.o atmel-sdhci-common.o obj-$(CONFIG_MCI_IMX) += imx.o obj-$(CONFIG_MCI_IMX_ESDHC) += imx-esdhc.o imx-esdhc-common.o pbl-$(CONFIG_MCI_IMX_ESDHC_PBL) += imx-esdhc-pbl.o imx-esdhc-common.o diff --git a/drivers/mci/atmel-sdhci-common.c b/drivers/mci/atmel-sdhci-common.c index b952462262..a83610c3d0 100644 --- a/drivers/mci/atmel-sdhci-common.c +++ b/drivers/mci/atmel-sdhci-common.c @@ -12,6 +12,16 @@ #include <common.h> #include <mci.h> +#include <mach/early_udelay.h> + +#ifdef __PBL__ +#define udelay early_udelay +#undef dev_err +#define dev_err(d, ...) pr_err(__VA_ARGS__) +#undef dev_warn +#define dev_warn(d, ...) pr_warn(__VA_ARGS__) +#endif + #include "atmel-sdhci.h" #define AT91_SDHCI_CA1R 0x44 /* Capabilities 1 Register */ @@ -83,25 +93,27 @@ exit: static int at91_sdhci_wait_for_done(struct at91_sdhci *host, u32 mask) { struct sdhci *sdhci = &host->sdhci; - u16 status; + u32 status; int ret; - ret = sdhci_read16_poll_timeout(sdhci, SDHCI_INT_NORMAL_STATUS, status, + ret = sdhci_read32_poll_timeout(sdhci, SDHCI_INT_STATUS, status, (status & mask) == mask || (status & SDHCI_INT_ERROR), USEC_PER_SEC); if (ret < 0) { - pr_err("SDHCI timeout while waiting for done\n"); + dev_err(host->dev, "SDHCI timeout while waiting for done\n"); return ret; } + if (status & SDHCI_INT_TIMEOUT) + return -ETIMEDOUT; + if (status & SDHCI_INT_ERROR) { - pr_err("SDHCI_INT_ERROR: 0x%08x\n", - sdhci_read16(sdhci, SDHCI_INT_ERROR_STATUS)); + dev_err(host->dev, "SDHCI_INT_STATUS: 0x%08x\n", status); return -EPERM; } - return status; + return status & 0xFFFF; } int at91_sdhci_send_command(struct at91_sdhci *host, struct mci_cmd *cmd, @@ -109,7 +121,8 @@ int at91_sdhci_send_command(struct at91_sdhci *host, struct mci_cmd *cmd, { unsigned command, xfer; struct sdhci *sdhci = &host->sdhci; - u32 mask, status, state; + u32 mask, state; + int status; int ret; /* Wait for idle before next command */ @@ -120,7 +133,7 @@ int at91_sdhci_send_command(struct at91_sdhci *host, struct mci_cmd *cmd, ret = sdhci_read32_poll_timeout(sdhci, SDHCI_PRESENT_STATE, state, !(state & mask), 100 * USEC_PER_MSEC); if (ret) { - pr_err("timeout while waiting for idle\n"); + dev_err(host->dev, "timeout while waiting for idle\n"); return ret; } @@ -147,28 +160,29 @@ int at91_sdhci_send_command(struct at91_sdhci *host, struct mci_cmd *cmd, sdhci_write16(sdhci, SDHCI_COMMAND, command); status = at91_sdhci_wait_for_done(host, mask); - if (status >= 0 && (status & (SDHCI_INT_ERROR | mask)) == mask) { - sdhci_read_response(sdhci, cmd); - sdhci_write32(sdhci, SDHCI_INT_STATUS, mask); - - if (data) - sdhci_transfer_data(sdhci, data); + if (status < 0) + goto error; - udelay(1000); + sdhci_read_response(sdhci, cmd); + sdhci_write32(sdhci, SDHCI_INT_STATUS, mask); - status = sdhci_read32(sdhci, SDHCI_INT_STATUS); - sdhci_write32(sdhci, SDHCI_INT_STATUS, ~0U); + if (data) + sdhci_transfer_data(sdhci, data); - return 0; - } + udelay(1000); status = sdhci_read32(sdhci, SDHCI_INT_STATUS); sdhci_write32(sdhci, SDHCI_INT_STATUS, ~0U); + return 0; + +error: + sdhci_write32(sdhci, SDHCI_INT_STATUS, ~0U); + sdhci_reset(sdhci, SDHCI_RESET_CMD); sdhci_reset(sdhci, SDHCI_RESET_DATA); - return status & SDHCI_INT_TIMEOUT ? -ETIMEDOUT : -ECOMM; + return status; } static void at91_sdhci_set_power(struct at91_sdhci *host, unsigned vdd) @@ -214,7 +228,7 @@ static int at91_sdhci_set_clock(struct at91_sdhci *host, unsigned clock) !(reg & present_mask), 100 * USEC_PER_MSEC); if (ret) { - pr_warn("Timeout waiting for CMD and DAT Inhibit bits\n"); + dev_warn(host->dev, "Timeout waiting for CMD and DAT Inhibit bits\n"); return ret; } @@ -258,7 +272,7 @@ static int at91_sdhci_set_clock(struct at91_sdhci *host, unsigned clock) clk & SDHCI_INTCLOCK_STABLE, 20 * USEC_PER_MSEC); if (ret) { - pr_warn("Timeout waiting for clock stable\n"); + dev_warn(host->dev, "Timeout waiting for clock stable\n"); return ret; } diff --git a/drivers/mci/atmel-sdhci-pbl.c b/drivers/mci/atmel-sdhci-pbl.c new file mode 100644 index 0000000000..626e4008fe --- /dev/null +++ b/drivers/mci/atmel-sdhci-pbl.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: BSD-1-Clause +/* + * Copyright (c) 2015, Atmel Corporation + * Copyright (c) 2019, Ahmad Fatoum, Pengutronix + * + * Atmel's name may not be used to endorse or promote products derived from + * this software without specific prior written permission. + */ + +#include <common.h> +#include <pbl.h> +#include <mci.h> +#include <debug_ll.h> +#include <mach/xload.h> +#include "atmel-sdhci.h" + +#include <mach/early_udelay.h> + +#ifdef __PBL__ +#define udelay early_udelay +#endif + +#define SECTOR_SIZE 512 +#define SUPPORT_MAX_BLOCKS 16U + +struct at91_sdhci_priv { + struct at91_sdhci host; + bool highcapacity_card; +}; + +static int sd_cmd_stop_transmission(struct at91_sdhci_priv *priv) +{ + struct mci_cmd cmd = { + .cmdidx = MMC_CMD_STOP_TRANSMISSION, + .resp_type = MMC_RSP_R1b, + }; + + return at91_sdhci_send_command(&priv->host, &cmd, NULL); +} + +static int sd_cmd_read_multiple_block(struct at91_sdhci_priv *priv, + void *buf, + unsigned int start, + unsigned int block_count) +{ + u16 block_len = SECTOR_SIZE; + struct mci_data data; + struct mci_cmd cmd = { + .cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK, + .resp_type = MMC_RSP_R1, + .cmdarg = start, + }; + + if (!priv->highcapacity_card) + cmd.cmdarg *= block_len; + + data.dest = buf; + data.flags = MMC_DATA_READ; + data.blocksize = block_len; + data.blocks = block_count; + + return at91_sdhci_send_command(&priv->host, &cmd, &data); +} + +static int at91_sdhci_bio_read(struct pbl_bio *bio, off_t start, + void *buf, unsigned int nblocks) +{ + struct at91_sdhci_priv *priv = bio->priv; + unsigned int blocks_done = 0; + unsigned int blocks; + unsigned int block_len = SECTOR_SIZE; + unsigned int blocks_read; + int ret; + + /* + * Refer to the at91sam9g20 datasheet: + * Figure 35-10. Read Function Flow Diagram + */ + + while (blocks_done < nblocks) { + blocks = min(nblocks - blocks_done, SUPPORT_MAX_BLOCKS); + + blocks_read = sd_cmd_read_multiple_block(priv, buf, + start + blocks_done, + blocks); + + ret = sd_cmd_stop_transmission(priv); + if (ret) + return ret; + + blocks_done += blocks_read; + + if (blocks_read != blocks) + break; + + buf += blocks * block_len; + } + + return blocks_done; +} + +static struct at91_sdhci_priv atmel_sdcard; + +int at91_sdhci_bio_init(struct pbl_bio *bio, void __iomem *base) +{ + struct at91_sdhci_priv *priv = &atmel_sdcard; + struct at91_sdhci *host = &priv->host; + struct mci_ios ios = { .bus_width = MMC_BUS_WIDTH_1, .clock = 25000000 }; + int ret; + + bio->priv = priv; + bio->read = at91_sdhci_bio_read; + + at91_sdhci_mmio_init(host, base); + + sdhci_reset(&host->sdhci, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + + ret = at91_sdhci_init(host, 240000000, true, true); + if (ret) + return ret; + + ret = at91_sdhci_set_ios(host, &ios); + + // FIXME can we determine this without leaving SD transfer mode? + priv->highcapacity_card = 1; + + return 0; +} diff --git a/drivers/mci/atmel-sdhci.c b/drivers/mci/atmel-sdhci.c index 6351186476..59cd002aae 100644 --- a/drivers/mci/atmel-sdhci.c +++ b/drivers/mci/atmel-sdhci.c @@ -46,7 +46,9 @@ static int at91_sdhci_mci_init(struct mci_host *mci, struct device_d *dev) struct sdhci *sdhci = &priv->host.sdhci; int ret; - ret = sdhci_reset(sdhci, SDHCI_RESET_ALL); + priv->host.dev = dev; + + ret = sdhci_reset(sdhci, SDHCI_RESET_CMD | SDHCI_RESET_DATA); if (ret) return ret; diff --git a/drivers/mci/atmel-sdhci.h b/drivers/mci/atmel-sdhci.h index 897ed4e4de..7032294647 100644 --- a/drivers/mci/atmel-sdhci.h +++ b/drivers/mci/atmel-sdhci.h @@ -11,6 +11,7 @@ struct at91_sdhci { struct sdhci sdhci; + struct device_d *dev; void __iomem *base; u32 caps_max_clock; }; diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index d9734ef588..cf83b6a15b 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -22,6 +22,12 @@ config WATCHDOG_AR9344 help Add support for watchdog on the QCA AR9344 SoC. +config WATCHDOG_AT91SAM9 + bool "Watchdog for AT91SAM9 and SAMA5 SoCs" + depends on ARCH_AT91 + help + Support for the watchdog in AT91SAM9X and SAMA5D{2,3,4} SoCs. + config WATCHDOG_EFI bool "Generic EFI Watchdog Driver" depends on EFI_BOOTUP diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 3af64db3f2..dc9842770a 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_WATCHDOG) += wd_core.o obj-$(CONFIG_WATCHDOG_AR9344) += ar9344_wdt.o +obj-$(CONFIG_WATCHDOG_AT91SAM9) += at91sam9_wdt.o obj-$(CONFIG_WATCHDOG_EFI) += efi_wdt.o obj-$(CONFIG_WATCHDOG_DAVINCI) += davinci_wdt.o obj-$(CONFIG_WATCHDOG_OMAP) += omap_wdt.o diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c new file mode 100644 index 0000000000..3f554bf47b --- /dev/null +++ b/drivers/watchdog/at91sam9_wdt.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Pengutronix, Ahmad Fatoum <a.fatoum@pengutronix.de> + */ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <watchdog.h> +#include <linux/clk.h> +#include <mach/at91_wdt.h> + +#define MIN_WDT_TIMEOUT 1 +#define MAX_WDT_TIMEOUT 16 +#define SECS_TO_WDOG_TICKS(s) ((s) ? (((s) << 8) - 1) : 0) + +struct at91sam9x_wdt { + struct watchdog wdd; + void __iomem *base; +}; + +static inline void at91sam9x_wdt_ping(struct at91sam9x_wdt *wdt) +{ + writel(AT91_WDT_WDRSTT | AT91_WDT_KEY, wdt->base + AT91_WDT_CR); +} + +static int at91sam9x_wdt_set_timeout(struct watchdog *wdd, unsigned timeout) +{ + struct at91sam9x_wdt *wdt = container_of(wdd, struct at91sam9x_wdt, wdd); + u32 mr_old, mr_new; + + mr_old = readl(wdt->base + AT91_WDT_MR); + + if (!timeout) { + mr_new = mr_old | AT91_WDT_WDDIS; + writel(mr_new, wdt->base + AT91_WDT_MR); + return 0; + } + + mr_new = AT91_WDT_WDRSTEN + | AT91_WDT_WDDBGHLT | AT91_WDT_WDIDLEHLT + | AT91_WDT_WDD + | (SECS_TO_WDOG_TICKS(timeout) & AT91_WDT_WDV); + + if (mr_new != mr_old) + writel(mr_new, wdt->base + AT91_WDT_MR); + + at91sam9x_wdt_ping(wdt); + return 0; +} + +static inline bool at91sam9x_wdt_is_disabled(struct at91sam9x_wdt *wdt) +{ + return readl(wdt->base + AT91_WDT_MR) & AT91_WDT_WDDIS; +} + +static int at91sam9x_wdt_probe(struct device_d *dev) +{ + struct at91sam9x_wdt *wdt; + struct resource *iores; + struct clk *clk; + int ret; + + wdt = xzalloc(sizeof(*wdt)); + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) { + dev_err(dev, "could not get watchdog memory region\n"); + return PTR_ERR(iores); + } + wdt->base = IOMEM(iores->start); + clk = clk_get(dev, NULL); + if (WARN_ON(IS_ERR(clk))) + return PTR_ERR(clk); + + clk_enable(clk); + + wdt->wdd.set_timeout = at91sam9x_wdt_set_timeout; + wdt->wdd.timeout_max = MAX_WDT_TIMEOUT; + wdt->wdd.hwdev = dev; + + if (at91sam9x_wdt_is_disabled(wdt)) + wdt->wdd.running = WDOG_HW_NOT_RUNNING; + else + wdt->wdd.running = WDOG_HW_RUNNING; + + ret = watchdog_register(&wdt->wdd); + if (ret) + free(wdt); + + return ret; +} + +static const __maybe_unused struct of_device_id at91sam9x_wdt_dt_ids[] = { + { .compatible = "atmel,at91sam9260-wdt", }, + { .compatible = "atmel,sama5d4-wdt", }, + { /* sentinel */ }, +}; + +static struct driver_d at91sam9x_wdt_driver = { + .name = "at91sam9x-wdt", + .of_compatible = DRV_OF_COMPAT(at91sam9x_wdt_dt_ids), + .probe = at91sam9x_wdt_probe, +}; + +static int __init at91sam9x_wdt_init(void) +{ + return platform_driver_register(&at91sam9x_wdt_driver); +} +device_initcall(at91sam9x_wdt_init); |