Skip to content

Commit

Permalink
mmc: bcm2835: Add downstream overclocking support
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
pelwell committed Jan 13, 2025
1 parent ba2e270 commit 99ae62c
Showing 1 changed file with 144 additions and 27 deletions.
171 changes: 144 additions & 27 deletions drivers/mmc/host/bcm2835.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
#include <linux/mmc/mmc.h>
#include <linux/mmc/sd.h>

#include <soc/bcm2835/raspberrypi-firmware.h>

#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 */
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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 &&
Expand Down Expand Up @@ -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);

Expand All @@ -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__);
Expand All @@ -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;
Expand All @@ -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);
Expand Down

0 comments on commit 99ae62c

Please sign in to comment.