From 99ae62c653ac92edabb6702252f2d1d9c2d2472c Mon Sep 17 00:00:00 2001 From: Phil Elwell Date: Mon, 13 Jan 2025 15:00:21 +0000 Subject: [PATCH] mmc: bcm2835: Add downstream overclocking support The principal differences between the downstream SDHOST driver and the version accepted upstream driver are that the upstream version loses the overclock support and DMA configuration via DT, but gains some tidying up (and maintenance by the upstream devs). Add the missing features (with the exception of the low-overhead logging) as a patch to the upstream driver. Signed-off-by: Phil Elwell --- drivers/mmc/host/bcm2835.c | 171 +++++++++++++++++++++++++++++++------ 1 file changed, 144 insertions(+), 27 deletions(-) diff --git a/drivers/mmc/host/bcm2835.c b/drivers/mmc/host/bcm2835.c index 746a60fac0f0a2..bc4a33c35f1768 100644 --- a/drivers/mmc/host/bcm2835.c +++ b/drivers/mmc/host/bcm2835.c @@ -48,6 +48,8 @@ #include #include +#include + #define SDCMD 0x00 /* Command to SD card - 16 R/W */ #define SDARG 0x04 /* Argument to SD card - 32 R/W */ #define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */ @@ -186,6 +188,14 @@ struct bcm2835_host { struct page *drain_page; u32 drain_offset; bool use_dma; + + /* Downstream additions */ + struct rpi_firmware *fw; + u32 pio_limit; /* Maximum block count for PIO (0 = always DMA) */ + u32 user_overclock_50; /* User's preferred overclock frequency */ + u32 overclock_50; /* Freq to use when 50MHz is requested (MHz) */ + u32 overclock; /* Current frequency if overclocked, else zero */ + bool reset_clock:1; /* Reset the clock for the next request */ }; static void bcm2835_dumpcmd(struct bcm2835_host *host, struct mmc_command *cmd, @@ -240,8 +250,11 @@ static void bcm2835_dumpregs(struct bcm2835_host *host) static void bcm2835_reset_internal(struct bcm2835_host *host) { + u32 cdiv = host->cdiv; u32 temp; + if (!cdiv) + cdiv = readl(host->ioaddr + SDCDIV); writel(SDVDD_POWER_OFF, host->ioaddr + SDVDD); writel(0, host->ioaddr + SDCMD); writel(0, host->ioaddr + SDARG); @@ -262,9 +275,8 @@ static void bcm2835_reset_internal(struct bcm2835_host *host) msleep(20); writel(SDVDD_POWER_ON, host->ioaddr + SDVDD); msleep(20); - host->clock = 0; writel(host->hcfg, host->ioaddr + SDHCFG); - writel(host->cdiv, host->ioaddr + SDCDIV); + writel(cdiv, host->ioaddr + SDCDIV); } static void bcm2835_reset(struct mmc_host *mmc) @@ -595,6 +607,25 @@ static void bcm2835_finish_request(struct bcm2835_host *host) mrq = host->mrq; + /* Drop the overclock after any data corruption, or after any + * error while overclocked. Ignore errors for status commands, + * as they are likely when a card is ejected. + */ + if (host->overclock) { + if ((mrq->cmd && mrq->cmd->error && + (mrq->cmd->opcode != MMC_SEND_STATUS)) || + (mrq->data && mrq->data->error) || + (mrq->stop && mrq->stop->error) || + (mrq->sbc && mrq->sbc->error)) { + host->overclock_50--; + dev_warn(&host->pdev->dev, + "reducing overclock due to errors\n"); + host->reset_clock = 1; + mrq->cmd->error = -ETIMEDOUT; + mrq->cmd->retries = 1; + } + } + host->mrq = NULL; host->cmd = NULL; host->data = NULL; @@ -1091,8 +1122,13 @@ static void bcm2835_dma_complete_work(struct work_struct *work) static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock) { struct mmc_host *mmc = mmc_from_priv(host); + const unsigned int input_clock = clock; + const unsigned int MHZ = 1000000; int div; + if (host->overclock_50 && (clock == 50*MHZ)) + clock = host->overclock_50 * MHZ + (MHZ - 1); + /* The SDCDIV register has 11 bits, and holds (div - 2). But * in data mode the max is 50MHz wihout a minimum, and only * the bottom 3 bits are used. Since the switch over is @@ -1114,38 +1150,78 @@ static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock) * clock divisor at all times. */ - if (clock < 100000) { - /* Can't stop the clock, but make it as slow as possible - * to show willing - */ - host->cdiv = SDCDIV_MAX_CDIV; - writel(host->cdiv, host->ioaddr + SDCDIV); - return; - } + if (host->fw) { + u32 msg[3] = { clock, 0, 0 }; - div = host->max_clk / clock; - if (div < 2) - div = 2; - if ((host->max_clk / div) > clock) - div++; - div -= 2; + rpi_firmware_property(host->fw, + RPI_FIRMWARE_SET_SDHOST_CLOCK, + &msg, sizeof(msg)); - if (div > SDCDIV_MAX_CDIV) - div = SDCDIV_MAX_CDIV; + clock = max(msg[1], msg[2]); + host->cdiv = 0; + } else { + if (clock < 100000) { + /* Can't stop the clock, but make it as slow as possible + * to show willing + */ + host->cdiv = SDCDIV_MAX_CDIV; + writel(host->cdiv, host->ioaddr + SDCDIV); + return; + } - clock = host->max_clk / (div + 2); - mmc->actual_clock = clock; + div = host->max_clk / clock; + if (div < 2) + div = 2; + if ((host->max_clk / div) > clock) + div++; + div -= 2; + + if (div > SDCDIV_MAX_CDIV) + div = SDCDIV_MAX_CDIV; + + clock = host->max_clk / (div + 2); + + host->cdiv = div; + writel(host->cdiv, host->ioaddr + SDCDIV); + } /* Calibrate some delays */ host->ns_per_fifo_word = (1000000000 / clock) * ((mmc->caps & MMC_CAP_4_BIT_DATA) ? 8 : 32); - host->cdiv = div; - writel(host->cdiv, host->ioaddr + SDCDIV); + if (input_clock == 50 * MHZ) { + if (clock > input_clock) { + /* Save the closest value, to make it easier + * to reduce in the event of error + */ + host->overclock_50 = (clock/MHZ); + + if (clock != host->overclock) { + pr_info("%s: overclocking to %dHz\n", + mmc_hostname(mmc), clock); + host->overclock = clock; + } + } else if (host->overclock) { + host->overclock = 0; + if (clock == 50 * MHZ) + pr_warn("%s: cancelling overclock\n", + mmc_hostname(mmc)); + } + } else if (input_clock == 0) { + /* Reset the preferred overclock when the clock is stopped. + * This always happens during initialisation. + */ + host->overclock_50 = host->user_overclock_50; + host->overclock = 0; + } /* Set the timeout to 500ms */ - writel(mmc->actual_clock / 2, host->ioaddr + SDTOUT); + writel(clock / 2, host->ioaddr + SDTOUT); + + mmc->actual_clock = clock; + host->clock = input_clock; + host->reset_clock = 0; } static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq) @@ -1175,6 +1251,9 @@ static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq) return; } + if (host->reset_clock) + bcm2835_set_clock(host, host->clock); + mutex_lock(&host->mutex); WARN_ON(host->mrq); @@ -1198,7 +1277,7 @@ static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq) return; } - if (host->use_dma && mrq->data && (mrq->data->blocks > PIO_THRESHOLD)) + if (host->use_dma && mrq->data && (mrq->data->blocks > host->pio_limit)) bcm2835_prepare_dma(host, mrq->data); host->use_sbc = !!mrq->sbc && host->mrq->data && @@ -1333,8 +1412,8 @@ static int bcm2835_add_host(struct bcm2835_host *host) } pio_limit_string[0] = '\0'; - if (host->use_dma && (PIO_THRESHOLD > 0)) - sprintf(pio_limit_string, " (>%d)", PIO_THRESHOLD); + if (host->use_dma && (host->pio_limit > 0)) + sprintf(pio_limit_string, " (>%d)", host->pio_limit); dev_info(dev, "loaded - DMA %s%s\n", host->use_dma ? "enabled" : "disabled", pio_limit_string); @@ -1344,10 +1423,13 @@ static int bcm2835_add_host(struct bcm2835_host *host) static int bcm2835_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct clk *clk; + struct device_node *node = dev->of_node; struct bcm2835_host *host; + struct rpi_firmware *fw; struct resource *iomem; + bool allow_dma = true; struct mmc_host *mmc; + struct clk *clk; int ret; dev_dbg(dev, "%s\n", __func__); @@ -1367,6 +1449,23 @@ static int bcm2835_probe(struct platform_device *pdev) } host->phys_addr = iomem->start; + host->pio_limit = PIO_THRESHOLD; + + if (node) { + /* Read any custom properties */ + of_property_read_u32(node, + "brcm,overclock-50", + &host->user_overclock_50); + of_property_read_u32(node, + "brcm,pio-limit", + &host->pio_limit); + allow_dma = + !of_property_read_bool(node, "brcm,force-pio"); + + /* Formally recognise the other way of disabling DMA */ + if (host->pio_limit == 0x7fffffff) + allow_dma = false; + } host->dma_chan = NULL; host->dma_desc = NULL; @@ -1389,6 +1488,24 @@ static int bcm2835_probe(struct platform_device *pdev) goto err; } + fw = rpi_firmware_get( + of_parse_phandle(dev->of_node, "firmware", 0)); + if (fw) { + u32 msg[3]; + + msg[0] = 0; + msg[1] = ~0; + msg[2] = ~0; + + rpi_firmware_property(fw, RPI_FIRMWARE_SET_SDHOST_CLOCK, + &msg, sizeof(msg)); + + if (msg[1] != ~0) + host->fw = fw; + else + rpi_firmware_put(fw); + } + host->max_clk = clk_get_rate(clk); host->irq = platform_get_irq(pdev, 0);