// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018 Oleksij Rempel <linux@rempel-privat.de>
*
* Driver for Alcor Micro AU6601 and AU6621 controllers
*/
/* Note: this driver was created without any documentation. Based
* on sniffing, testing and in some cases mimic of original driver.
* As soon as some one with documentation or more experience in SD/MMC, or
* reverse engineering then me, please review this driver and question every
* thing what I did. 2018 Oleksij Rempel <linux@rempel-privat.de>
*/
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/pm.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/alcor_pci.h>
enum alcor_cookie {
COOKIE_UNMAPPED,
COOKIE_PRE_MAPPED,
COOKIE_MAPPED,
};
struct alcor_pll_conf {
unsigned int clk_src_freq;
unsigned int clk_src_reg;
unsigned int min_div;
unsigned int max_div;
};
struct alcor_sdmmc_host {
struct device *dev;
struct alcor_pci_priv *alcor_pci;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
unsigned int dma_on:1;
struct mutex cmd_mutex;
struct delayed_work timeout_work;
struct sg_mapping_iter sg_miter; /* SG state for PIO */
struct scatterlist *sg;
unsigned int blocks; /* remaining PIO blocks */
int sg_count;
u32 irq_status_sd;
unsigned char cur_power_mode;
};
static const struct alcor_pll_conf alcor_pll_cfg[] = {
/* MHZ, CLK src, max div, min div */
{ 31250000, AU6601_CLK_31_25_MHZ, 1, 511},
{ 48000000, AU6601_CLK_48_MHZ, 1, 511},
{125000000, AU6601_CLK_125_MHZ, 1, 511},
{384000000, AU6601_CLK_384_MHZ, 1, 511},
};
static inline void alcor_rmw8(struct alcor_sdmmc_host *host, unsigned int addr,
u8 clear, u8 set)
{
struct alcor_pci_priv *priv = host->alcor_pci;
u32 var;
var = alcor_read8(priv, addr);
var &= ~clear;
var |= set;
alcor_write8(priv, var, addr);
}
/* As soon as irqs are masked, some status updates may be missed.
* Use this with care.
*/
static inline void alcor_mask_sd_irqs(struct alcor_sdmmc_host *host)
{
struct alcor_pci_priv *priv = host->alcor_pci;
alcor_write32(priv, 0, AU6601_REG_INT_ENABLE);
}
static inline void alcor_unmask_sd_irqs(struct alcor_sdmmc_host *host)
{
struct alcor_pci_priv *priv = host->alcor_pci;
alcor_write32(priv, AU6601_INT_CMD_MASK | AU6601_INT_DATA_MASK |
AU6601_INT_CARD_INSERT | AU6601_INT_CARD_REMOVE |
AU6601_INT_OVER_CURRENT_ERR,
AU6601_REG_INT_ENABLE);
}
static void alcor_reset(struct alcor_sdmmc_host *host, u8 val)
{
struct alcor_pci_priv *priv = host->alcor_pci;
int i;
alcor_write8(priv, val | AU6601_BUF_CTRL_RESET,
AU6601_REG_SW_RESET);
for (i = 0; i < 100; i++) {
if (!(alcor_read8(priv, AU6601_REG_SW_RESET) & val))
return;
udelay(50);
}
dev_err(host->dev, "%s: timeout\n", __func__);
}
/*
* Perform DMA I/O of a single page.
*/
static void alcor_data_set_dma(struct alcor_sdmmc_host *host)
{
struct alcor_pci_priv *priv = host->alcor_pci;
u32 addr;
if (!host->sg_count)
return;
if (!host->sg) {
dev_err(host->dev, "have blocks, but no SG\n");
return;
}
if (!sg_dma_len(host->sg)) {
dev_err(host->dev, "DMA SG len == 0\n");
return;
}
addr = (u32)sg_dma_address(host->sg);
alcor_write32(priv, addr, AU6601_REG_SDMA_ADDR);
host->sg = sg_next(host->sg);
host->sg_count--;
}
static void alcor_trigger_data_transfer(struct alcor_sdmmc_host *host)
{
struct