Compare commits

...

10 Commits

Author SHA1 Message Date
Swee Aun Khor
9074d7744f Enhance SDIO tuning algorithm
Improve the SDIO tuning algorithm to increase stability and
performance under low-temperature conditions.

Signed-off-by: Swee Aun Khor <sakhor@efinixinc.com>
2026-08-09 22:44:47 -07:00
Swee Aun Khor
334eb3ba15 Add a new SDIO controller driver for Efinix devices
The driver supports UHS-I bus speed modes: SDR25, DDR50, and SDR104.
By default, the controller operates in SDR25 mode. Higher-speed modes
can be enabled via the Device Tree by adding the following properties
to the SDIO node:

  - sd-uhs-ddr50
  - sd-uhs-sdr104

When these properties are present, the driver negotiates the highest
supported UHS mode with the card and host.

Signed-off-by: Swee Aun Khor <sakhor@efinixinc.com>
2026-08-09 22:44:47 -07:00
Kefeng Wang
d7b360824e riscv: Enable CMA support
riscv has selected HAVE_DMA_CONTIGUOUS, but don't call
dma_contiguous_reserve(). let's enable CMA as expect.

Signed-off-by: Kefeng Wang <wangkefeng.wang@huawei.com>
2026-08-09 22:44:46 -07:00
Teoh Choon Zone
7c649c3ce5 mmc: Add Efinix eMMC driver 2026-08-09 22:42:42 -07:00
Mohamad Noor Alim Hussin
f0ec6f6955 drivers: add efinix watchdog driver 2026-08-09 22:42:42 -07:00
Mohamad Noor Alim Hussin
d31b653b2f drivers/mtd/spi-nor: add support for is25wp512m spi flash device
Ti375C529 use spi flash is25wp512m. This spi flash did not recognize by
the kernel as it missing the device id within spi-flash device driver.

Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
2026-08-09 22:42:42 -07:00
Mohamad Noor Alim Hussin
b6b404e6d1 fb: add Efinix framebuffer driver 2026-08-09 22:42:42 -07:00
Mohamad Noor Alim Hussin
89d4b5521f dma: add Efinix DMA controller driver 2026-08-09 22:42:42 -07:00
Mohamad Noor Alim Hussin
6f082dfd7a spi-spinal-lib: fix spi driver to support dummy clock cycle
SPI flash require 8 dummy clock cycle during read operation
before it can receive the data. However, this might not needed
by other SPI device such as SD card when operate in SPI mode.
Thus, a new device tree property called 'dummy-cycle' is added
to resolve this issue. This optional device tree property is
needed for controlling the SPI flash device.

Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
2026-08-09 22:42:41 -07:00
Mohamad Noor Alim Hussin
0debccc414 phy: realtek: enable master mode configuration for RTL8211F
The PHY has an issue when operating in gigabit slave mode.
It not able to transmit or receive any packet in the slave
mode. Thus, this patch enable the master mode configuration
of the phy.

Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
2026-08-09 22:42:41 -07:00
25 changed files with 7049 additions and 59 deletions

View File

@@ -13,6 +13,7 @@
#include <linux/of_fdt.h>
#include <linux/libfdt.h>
#include <linux/set_memory.h>
#include <linux/dma-map-ops.h>
#include <asm/fixmap.h>
#include <asm/tlbflush.h>
@@ -41,13 +42,14 @@ struct pt_alloc_ops {
#endif
};
static phys_addr_t dma32_phys_limit __ro_after_init;
static void __init zone_sizes_init(void)
{
unsigned long max_zone_pfns[MAX_NR_ZONES] = { 0, };
#ifdef CONFIG_ZONE_DMA32
max_zone_pfns[ZONE_DMA32] = PFN_DOWN(min(4UL * SZ_1G,
(unsigned long) PFN_PHYS(max_low_pfn)));
max_zone_pfns[ZONE_DMA32] = PFN_DOWN(dma32_phys_limit);
#endif
max_zone_pfns[ZONE_NORMAL] = max_low_pfn;
@@ -193,6 +195,7 @@ void __init setup_bootmem(void)
max_pfn = PFN_DOWN(dram_end);
max_low_pfn = max_pfn;
dma32_phys_limit = min(4UL * SZ_1G, (unsigned long)PFN_PHYS(max_low_pfn));
set_max_mapnr(max_low_pfn);
#ifdef CONFIG_BLK_DEV_INITRD
@@ -206,6 +209,7 @@ void __init setup_bootmem(void)
memblock_reserve(dtb_early_pa, fdt_totalsize(dtb_early_va));
early_init_fdt_scan_reserved_mem();
dma_contiguous_reserve(dma32_phys_limit);
memblock_allow_resize();
memblock_dump_all();
}

View File

@@ -187,6 +187,13 @@ config DW_AXI_DMAC
NOTE: This driver wasn't tested on 64 bit platform because
of lack 64 bit platform with Synopsys DW AXI DMAC.
config EFINIX_DMA
bool "Efinix DMA support"
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
help
Enable support for Efinix DMA controller
config EP93XX_DMA
bool "Cirrus Logic EP93xx DMA support"
depends on ARCH_EP93XX || COMPILE_TEST

View File

@@ -29,6 +29,7 @@ obj-$(CONFIG_DMA_SUN6I) += sun6i-dma.o
obj-$(CONFIG_DW_AXI_DMAC) += dw-axi-dmac/
obj-$(CONFIG_DW_DMAC_CORE) += dw/
obj-$(CONFIG_DW_EDMA) += dw-edma/
obj-$(CONFIG_EFINIX_DMA) += efx_dma.o
obj-$(CONFIG_EP93XX_DMA) += ep93xx_dma.o
obj-$(CONFIG_FSL_DMA) += fsldma.o
obj-$(CONFIG_FSL_EDMA) += fsl-edma.o fsl-edma-common.o

889
drivers/dma/efx_dma.c Normal file
View File

@@ -0,0 +1,889 @@
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/of.h>
#include <linux/of_dma.h>
#include <linux/of_irq.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include "dmaengine.h"
#include "virt-dma.h"
#define EFX_DMA_CHANNEL_INPUT_ADDRESS 0x00
#define EFX_DMA_CHANNEL_INPUT_STREAM 0x08
#define EFX_DMA_CHANNEL_INPUT_CONFIG 0x0c
#define EFX_DMA_CHANNEL_INPUT_CONFIG_MEMORY (1 << 12)
#define EFX_DMA_CHANNEL_INPUT_CONFIG_STREAM 0x0
#define EFX_DMA_CHANNEL_INPUT_CONFIG_COMPLETION_ON_PACKET (1 << 13)
#define EFX_DMA_CHANNEL_INPUT_CONFIG_WAIT_ON_PACKET (1 << 14)
#define EFX_DMA_CHANNEL_OUTPUT_ADDRESS 0x10
#define EFX_DMA_CHANNEL_OUTPUT_STREAM 0x18
#define EFX_DMA_CHANNEL_OUTPUT_CONFIG 0x1c
#define EFX_DMA_CHANNEL_OUTPUT_CONFIG_MEMORY (1 << 12)
#define EFX_DMA_CHANNEL_OUTPUT_CONFIG_STREAM 0x0
#define EFX_DMA_CHANNEL_OUTPUT_CONFIG_LAST (1 << 13)
#define EFX_DMA_CHANNEL_DIRECT_BYTES 0x20
#define EFX_DMA_CHANNEL_STATUS 0x2c
#define EFX_DMA_CHANNEL_STATUS_DIRECT_START (1 << 0)
#define EFX_DMA_CHANNEL_STATUS_BUSY (1 << 0)
#define EFX_DMA_CHANNEL_STATUS_SELF_RESTART (1 << 1)
#define EFX_DMA_CHANNEL_STATUS_STOP (1 << 2)
#define EFX_DMA_CHANNEL_STATUS_LINKED_LIST_START (1 << 4)
#define EFX_DMA_CHANNEL_FIFO 0x40
#define EFX_DMA_CHANNEL_PRIORITY 0x44
#define EFX_DMA_CHANNEL_INTERRUPT_ENABLE 0x50
#define EFX_DMA_CHANNEL_INTERRUPT_PENDING 0x54
// Interrupt at the end of each descriptor
#define EFX_DMA_CHANNEL_INTERRUPT_DESCRIPTOR_COMPLETION_MASK (1 << 0)
// Interrupt at the middle of each descriptor, require the half_completion_interrpt
// option to be enabled for the channel
#define EFX_DMA_CHANNEL_INTERRUPT_DESCRIPTOR_COMPLETION_HALF_MASK (1 << 1)
// Interrupt when the channel is going off (not busy anymore)
#define EFX_DMA_CHANNEL_INTERRUPT_CHANNEL_COMPLETION_MASK (1 << 2)
// Interrupt each time that a linked list's descriptor stats field is updated
#define EFX_DMA_CHANNEL_INTERRUPT_LINKED_LIST_UPDATE_MASK (1 << 3)
// Interrupt each time a S -> M channel has done transferring a packet into the memory
#define EFX_DMA_CHANNEL_INTERRUPT_INPUT_PACKET_MASK (1 << 4)
#define EFX_DMA_CHANNEL_PROGRESS_BYTES 0x60
#define EFX_DMA_CHANNEL_LINKED_LIST_HEAD 0x70
#define EFX_DMA_CHANNEL_LINKED_LIST_FROM_SG_BUS 0x78
#define EFX_DMA_DESCRIPTOR_CONTROL_BYTES 0x7FFFFFF
#define EFX_DMA_DESCRIPTOR_CONTROL_END_OF_PACKET (1 << 30)
#define EFX_DMA_DESCRIPTOR_NO_COMPLETION (1 << 31)
#define EFX_DMA_DESCRIPTOR_STATUS_BYTES 0x7FFFFFF
#define EFX_DMA_DESCRIPTOR_STATUS_END_OF_PACKET (1 << 30)
#define EFX_DMA_DESCRIPTOR_STATUS_COMPLETED (1 << 31)
// DMA Hardware descriptor
struct efx_dma_hw_desc {
u32 status;
u32 control;
u64 src_addr;
u64 dst_addr;
u64 next; // physical address of next descriptor
} __aligned(64);
// Per transfer descriptor
struct efx_dma_desc {
struct virt_dma_desc vdesc;
struct efx_dma_hw_desc *hw_desc; // Pointer to the struct efx_dma_hw_desc
dma_addr_t dma_handle; // DMA address of struct efx_dma_hw_desc
struct list_head node; // List of struct efx_dma_hw_desc
size_t segments; // Number of DMA descriptor
bool cyclic; // True for cyclic transfer
enum dma_transfer_direction direction;
struct scatterlist *sg;
struct page **pages;
};
// DMA channel specific data
struct efx_dma_chan {
const char *name; // Channel name
void __iomem *reg; // based address of DMA channel
size_t chan_id; // Channel ID
struct virt_dma_chan vchan;
struct efx_dma_priv *priv; // Pointer to the DMA controller
struct dma_slave_config *cfg; // Channel specific configuration
u32 priority; // Priority number of DMA channel
size_t irq; // IRQ number used by the DMA channel
struct efx_dma_desc *head_desc; // First DMA descriptor
struct list_head pending_list; // List of all DMA descriptor
spinlock_t lock; // Lock for manipulating pending_list
};
// DMA controller private data
struct efx_dma_priv {
struct device *dev;
void __iomem *base; // DMA controller based address
struct dma_device dma_dev;
struct efx_dma_chan *dchan; // Pointer to the DMA channel
u32 chan_count; // Number of DMA channel
};
static inline struct efx_dma_chan *to_efx_dma_chan(struct dma_chan *chan)
{
return container_of(chan, struct efx_dma_chan, vchan.chan);
}
static inline struct efx_dma_priv *to_efx_dma_priv(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
return dchan->priv;
}
static void efx_dma_input_memory(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct efx_dma_hw_desc *hw_desc = dchan->head_desc->hw_desc;
u32 byte_per_burst = 0;
if (dchan->cfg)
byte_per_burst = dchan->cfg->src_maxburst;
else
byte_per_burst = chan->device->max_burst;
iowrite32(hw_desc->src_addr, dchan->reg + EFX_DMA_CHANNEL_INPUT_ADDRESS);
iowrite32(EFX_DMA_CHANNEL_INPUT_CONFIG_MEMORY | ((byte_per_burst - 1) & 0xFFF),
dchan->reg + EFX_DMA_CHANNEL_INPUT_CONFIG);
}
static void efx_dma_output_memory(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct efx_dma_hw_desc *hw_desc = dchan->head_desc->hw_desc;
u32 len = hw_desc->control & 0x1FFFFFF;
iowrite32(hw_desc->dst_addr, dchan->reg + EFX_DMA_CHANNEL_OUTPUT_ADDRESS);
iowrite32(EFX_DMA_CHANNEL_OUTPUT_CONFIG_MEMORY | ((len - 1) & 0xFFF),
dchan->reg + EFX_DMA_CHANNEL_OUTPUT_CONFIG);
}
static void efx_dma_input_stream(struct dma_chan *chan, u32 wait_on_packet, u32 completion_on_packet)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
completion_on_packet = completion_on_packet ? EFX_DMA_CHANNEL_INPUT_CONFIG_COMPLETION_ON_PACKET : 0;
wait_on_packet = wait_on_packet ? EFX_DMA_CHANNEL_INPUT_CONFIG_WAIT_ON_PACKET : 0;
iowrite32(0, dchan->reg + EFX_DMA_CHANNEL_INPUT_STREAM);
iowrite32(EFX_DMA_CHANNEL_INPUT_CONFIG_STREAM
| completion_on_packet | wait_on_packet,
dchan->reg + EFX_DMA_CHANNEL_INPUT_CONFIG);
}
static void efx_dma_output_stream(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
iowrite32(0, dchan->reg + EFX_DMA_CHANNEL_OUTPUT_STREAM);
iowrite32(EFX_DMA_CHANNEL_OUTPUT_CONFIG_LAST | EFX_DMA_CHANNEL_OUTPUT_CONFIG_STREAM,
dchan->reg + EFX_DMA_CHANNEL_OUTPUT_CONFIG);
}
static void efx_dma_linked_list_start(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
iowrite32(dchan->head_desc->dma_handle, dchan->reg + EFX_DMA_CHANNEL_LINKED_LIST_HEAD);
iowrite32(0, dchan->reg + EFX_DMA_CHANNEL_LINKED_LIST_FROM_SG_BUS);
iowrite32(EFX_DMA_CHANNEL_STATUS_LINKED_LIST_START, dchan->reg + EFX_DMA_CHANNEL_STATUS);
}
static void efx_dma_stop_channel(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
iowrite32(EFX_DMA_CHANNEL_STATUS_STOP, dchan->reg + EFX_DMA_CHANNEL_STATUS);
}
static bool efx_dma_busy(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
u32 busy;
busy = readl(dchan->reg + EFX_DMA_CHANNEL_STATUS) & EFX_DMA_CHANNEL_STATUS_BUSY;
return busy ? true : false;
}
static inline void efx_dma_interrupt_pending_clear(struct dma_chan *chan, u32 mask)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
iowrite32(mask, dchan->reg + EFX_DMA_CHANNEL_INTERRUPT_PENDING);
}
static void efx_dma_interrupt_config(struct dma_chan *chan, u32 mask)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
efx_dma_interrupt_pending_clear(chan, 0xFFFFFFFF);
iowrite32(mask, dchan->reg + EFX_DMA_CHANNEL_INTERRUPT_ENABLE);
}
static void efx_dma_set_channel_priority(struct efx_dma_chan *dchan)
{
iowrite32(dchan->priority, dchan->reg + EFX_DMA_CHANNEL_PRIORITY);
}
static void efx_dma_configure_registers(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct efx_dma_desc *desc = dchan->head_desc;
if (desc->direction == DMA_DEV_TO_MEM) {
efx_dma_output_memory(chan);
efx_dma_input_stream(chan, 1, 0);
} else if (desc->direction == DMA_MEM_TO_DEV) {
efx_dma_input_memory(chan);
efx_dma_output_stream(chan);
} else if (desc->direction == DMA_MEM_TO_MEM) {
efx_dma_input_memory(chan);
efx_dma_output_memory(chan);
}
}
static void efx_dma_start_transfer(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct virt_dma_desc *vdesc;
vdesc = vchan_next_desc(&dchan->vchan);
if (!vdesc)
return;
efx_dma_configure_registers(chan);
efx_dma_linked_list_start(chan);
}
static irqreturn_t efx_dma_interrupt_handler(int irq, void *dev_id)
{
struct efx_dma_chan *dchan = (struct efx_dma_chan *)dev_id;
struct efx_dma_desc *desc = dchan->head_desc;
struct dma_chan *chan = &dchan->vchan.chan;
unsigned long flags;
u32 pending;
spin_lock_irqsave(&dchan->vchan.lock, flags);
pending = readl(dchan->reg + EFX_DMA_CHANNEL_INTERRUPT_PENDING);
if (pending & EFX_DMA_CHANNEL_INTERRUPT_DESCRIPTOR_COMPLETION_MASK)
efx_dma_interrupt_pending_clear(chan, EFX_DMA_CHANNEL_INTERRUPT_DESCRIPTOR_COMPLETION_MASK);
if (pending & EFX_DMA_CHANNEL_INTERRUPT_CHANNEL_COMPLETION_MASK)
efx_dma_interrupt_pending_clear(chan, EFX_DMA_CHANNEL_INTERRUPT_CHANNEL_COMPLETION_MASK);
vchan_cookie_complete(&desc->vdesc);
// Unmask the interrupt
efx_dma_interrupt_pending_clear(chan, 0x0);
pending = readl(dchan->reg + EFX_DMA_CHANNEL_INTERRUPT_PENDING);
spin_unlock_irqrestore(&dchan->vchan.lock, flags);
return IRQ_HANDLED;
}
static int efx_dma_alloc_chan_resources(struct dma_chan *chan)
{
return 0;
}
static void efx_dma_free_chan_resources(struct dma_chan *chan)
{
}
static void efx_dma_dump_pending_list(struct dma_chan *chan)
{
struct efx_dma_priv *priv = to_efx_dma_priv(chan);
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct efx_dma_desc *desc;
struct device *dev = priv->dev;
int i = 0, count = 0;
int limit = 5;
// Count number of descriptor in the pending_list
spin_lock(&dchan->lock);
list_for_each_entry(desc, &dchan->pending_list, node)
count++;
spin_unlock(&dchan->lock);
if (count == 0) {
dev_dbg(dev, "Pending list is empty\n");
return;
}
dev_dbg(dev, "Pending list has %d descriptors\n", count);
// Print first 5 and last 5 descriptors
spin_lock(&dchan->lock);
list_for_each_entry(desc, &dchan->pending_list, node) {
if (i < limit || i >= count - limit) {
pr_debug("%s: Descriptor %d:\n", __func__, i);
pr_debug(" desc=%p, hw_desc=%p, dma_handle=0x%llx\n",
desc, desc->hw_desc, (unsigned long long)desc->dma_handle);
if (desc->hw_desc) {
pr_debug(" status=0x%x, control=0x%x\n",
desc->hw_desc->status, desc->hw_desc->control);
pr_debug(" src_addr=0x%llx, dst_addr=0x%llx, next=0x%llx\n",
(unsigned long long)desc->hw_desc->src_addr,
(unsigned long long)desc->hw_desc->dst_addr,
(unsigned long long)desc->hw_desc->next);
}
pr_debug(" cyclic=%d, direction=%d, segments=%zu\n",
desc->cyclic, desc->direction, desc->segments);
}
i++;
}
spin_unlock(&dchan->lock);
}
static void efx_dma_free_hw_desc_chain(struct dma_chan *chan)
{
struct efx_dma_priv *priv = to_efx_dma_priv(chan);
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct efx_dma_desc *desc, *tmp;
struct device *dev = priv->dev;
int count = 0;
int limit = 5, i = 0;
// Count number of descriptor in the pending_list
spin_lock(&dchan->lock);
list_for_each_entry(desc, &dchan->pending_list, node)
count++;
spin_unlock(&dchan->lock);
if (count == 0) {
dev_info(dev, "Pending list is empty\n");
return;
}
dev_info(dev, "Freeing %d descriptors\n", count);
spin_lock(&dchan->lock);
list_for_each_entry_safe(desc, tmp, &dchan->pending_list, node) {
list_del(&desc->node);
if (desc->hw_desc) {
dma_free_coherent(dev, sizeof(struct efx_dma_hw_desc),
desc->hw_desc, desc->dma_handle);
if (i < limit || i >= count - limit) {
pr_debug(" Freeing descriptor: %d\n", i);
pr_debug(" free hw_desc=%p, dma_handle=0x%llx\n",desc->hw_desc,
(unsigned long long)desc->dma_handle);
}
}
kfree(desc);
if (i < limit || i >= count - limit)
pr_debug(" desc=%p\n",desc);
i++;
}
dchan->head_desc = NULL;
spin_unlock(&dchan->lock);
}
static void efx_dma_desc_free(struct virt_dma_desc *vd)
{
struct dma_chan *chan = vd->tx.chan;
efx_dma_free_hw_desc_chain(chan);
}
static void efx_dma_hw_desc_address_control(struct efx_dma_chan *dchan,
struct efx_dma_desc *desc, dma_addr_t src_addr, dma_addr_t dst_addr,
size_t period_len, struct scatterlist *sg, size_t i)
{
struct efx_dma_hw_desc *hw_desc = desc->hw_desc;
dma_addr_t segment_addr;
u32 buf_len;
u32 control = desc->cyclic ? EFX_DMA_DESCRIPTOR_NO_COMPLETION
: EFX_DMA_DESCRIPTOR_CONTROL_END_OF_PACKET;
hw_desc->status = 0;
if (sg) {
buf_len = sg_dma_len(sg);
segment_addr = sg_dma_address(sg);
} else {
buf_len = period_len - 1;
segment_addr = src_addr + i * period_len;
}
hw_desc->control = (u32)(control | buf_len);
// Configure last hw_desc
if (i == (desc->segments - 1)) {
if (desc->cyclic) {
hw_desc->next = dchan->head_desc->dma_handle;
} else {
hw_desc->status = EFX_DMA_DESCRIPTOR_STATUS_COMPLETED;
}
}
if (desc->direction == DMA_DEV_TO_MEM) {
hw_desc->src_addr = 0;
hw_desc->dst_addr = segment_addr;
} else if (desc->direction == DMA_MEM_TO_DEV) {
hw_desc->src_addr = segment_addr;
hw_desc->dst_addr = 0;
} else if (desc->direction == DMA_MEM_TO_MEM) {
hw_desc->src_addr = src_addr + i * period_len;
hw_desc->dst_addr = dst_addr + i * period_len;
}
}
static int efx_dma_hw_desc_init(struct dma_chan *chan, dma_addr_t src_addr, dma_addr_t dst_addr,
struct scatterlist *sg, size_t len, size_t period_len,
enum dma_transfer_direction direction, bool cyclic)
{
struct efx_dma_priv *priv = to_efx_dma_priv(chan);
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct efx_dma_desc *desc = NULL, *prev_desc = NULL;
struct efx_dma_hw_desc *hw_desc = NULL;
struct device *dev = priv->dev;
struct scatterlist *sgl;
size_t i;
int ret = 0;
for (i = 0; i < len; i++) {
// Allocate memory for desc
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
if (!desc) {
ret = -ENOMEM;
dev_err(dev, "Failed to allocate memory for descriptor\n");
goto err_free_desc_init;
}
desc->cyclic = cyclic;
desc->segments = len;
desc->direction = direction;
INIT_LIST_HEAD(&desc->node);
// Allocate memory for hw_desc
hw_desc = dma_alloc_coherent(dev, sizeof(*hw_desc),
&desc->dma_handle, GFP_KERNEL);
if (!hw_desc) {
dev_err(dev, "Failed to allocate memory for hw descriptor\n");
kfree(desc);
ret = -ENOMEM;
goto err_free_desc_init;
}
desc->hw_desc = hw_desc;
// Save head descriptor
if (i == 0)
dchan->head_desc = desc;
// Set the scatterlist pointer for sg mode. For cylic, sgl is NULL
sgl = sg ? &sg[i] : NULL;
// Configure the hw descriptor addresses and control
efx_dma_hw_desc_address_control(dchan, desc, src_addr, dst_addr, period_len, sgl, i);
// Chain descriptor
if (prev_desc)
prev_desc->hw_desc->next = desc->dma_handle;
prev_desc = desc;
// Add to pending list
spin_lock(&dchan->lock);
list_add_tail(&desc->node, &dchan->pending_list);
spin_unlock(&dchan->lock);
}
// Debugging
efx_dma_dump_pending_list(chan);
return 0;
err_free_desc_init:
efx_dma_free_hw_desc_chain(chan);
return ret;
}
static struct dma_async_tx_descriptor *efx_dma_prep_slave_sg(
struct dma_chan *chan,
struct scatterlist *sg, unsigned int sg_len,
enum dma_transfer_direction direction,
unsigned long flags, void *context)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct device *dev = dchan->priv->dev;
struct efx_dma_desc *desc = NULL;
int ret;
bool cyclic = false;
// Validate input
if (unlikely(!chan || !sg || sg_len <= 0))
return NULL;
// sg_len +1 to inlcude the last status of the descriptor
sg_len += 1;
if (flags & DMA_CTRL_REUSE) {
cyclic = true;
sg_len -= 1; // Cyclic transfer does not require additional descriptor
flags &= ~DMA_CTRL_REUSE;
}
// Initialize hardware descriptor. Set cyclic to false
ret = efx_dma_hw_desc_init(chan, 0, 0, sg, sg_len, 0, direction, cyclic);
if (ret) {
dev_err(dev, "Failed to initialize hardware descriptor\n");
return NULL;
}
// Get head descriptor
desc = dchan->head_desc;
if (!desc) {
dev_err(dev, "No head descriptor found\n");
return NULL;
}
// Configure interrupt
if (flags & DMA_PREP_INTERRUPT)
efx_dma_interrupt_config(chan, EFX_DMA_CHANNEL_INTERRUPT_CHANNEL_COMPLETION_MASK);
// Prepare virtual DMA descriptor
dchan->vchan.cyclic = &desc->vdesc;
return vchan_tx_prep(&dchan->vchan, &desc->vdesc, flags);
}
static struct dma_async_tx_descriptor *efx_dma_prep_cyclic(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_transfer_direction direction,
unsigned long flags)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct device *dev = dchan->priv->dev;
struct efx_dma_desc *desc = NULL;
size_t segments = buf_len / period_len;
int ret;
if (unlikely(!chan || !buf_addr || buf_len == 0 || period_len > buf_len))
return NULL;
ret = efx_dma_hw_desc_init(chan, buf_addr, 0, NULL, segments, period_len, direction, true);
if (ret)
return NULL;
// Get head descriptor
desc = dchan->head_desc;
if (!desc) {
dev_err(dev, "No head descriptor found\n");
return NULL;
}
dchan->vchan.cyclic = &desc->vdesc;
return vchan_tx_prep(&dchan->vchan, &desc->vdesc, flags);
}
static struct dma_async_tx_descriptor *efx_dma_prep_dma_memcpy(struct dma_chan *chan,
dma_addr_t dst_addr, dma_addr_t src_addr, size_t len,
unsigned long flags)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct efx_dma_desc *desc = NULL;
struct device *dev = dchan->priv->dev;
size_t segments;
int ret;
// Validate input
if (unlikely(!chan || !dst_addr || !src_addr || !len))
return NULL;
if (len < PAGE_SIZE)
segments = 1;
else
segments = (len / PAGE_SIZE);
// Increase segments by 1 to include the last status of the descriptor
segments += 1;
ret = efx_dma_hw_desc_init(chan, src_addr, dst_addr, NULL, len, segments, DMA_MEM_TO_MEM, false);
if (ret) {
return NULL;
}
// Get head descriptor
desc = dchan->head_desc;
if (!desc) {
dev_err(dev, "No head descriptor found\n");
return NULL;
}
dchan->vchan.cyclic = &desc->vdesc;
return vchan_tx_prep(&dchan->vchan, &desc->vdesc, flags);
}
static void efx_dma_issue_pending(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
unsigned long flags;
spin_lock_irqsave(&dchan->vchan.lock, flags);
if (vchan_issue_pending(&dchan->vchan))
efx_dma_start_transfer(chan);
spin_unlock_irqrestore(&dchan->vchan.lock, flags);
}
static int efx_dma_device_config(struct dma_chan *chan,
struct dma_slave_config *config)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
dchan->cfg = config;
return 0;
}
static enum dma_status efx_dma_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
if (efx_dma_busy(chan))
return DMA_IN_PROGRESS;
if (cookie < DMA_MIN_COOKIE)
return DMA_ERROR;
if (chan->completed_cookie == cookie)
return DMA_COMPLETE;
return DMA_COMPLETE;
}
static int efx_dma_terminate_all(struct dma_chan *chan)
{
struct efx_dma_chan *dchan = to_efx_dma_chan(chan);
struct virt_dma_chan *vchan = &dchan->vchan;
struct device *dev = dchan->priv->dev;
if (dchan) {
// Stop all transfer of the DMA channels
efx_dma_stop_channel(&dchan->vchan.chan);
// Disabled interrupt
efx_dma_interrupt_config(chan, 0);
// Terminate all virtual DMA descriptors
//vchan_free_chan_resources(vchan);
// Free all pending descriptors
if (dchan->head_desc)
efx_dma_free_hw_desc_chain(&dchan->vchan.chan);
// Clear cyclic pointer
vchan->cyclic = NULL;
dchan->head_desc = NULL;
dev_info(dev, "DMA channel %s terminated and cleaned up\n", dchan->name);
}
return 0;
}
static void efx_dma_release(struct dma_device *dev)
{
}
static int efx_dma_chan_probe(struct platform_device *pdev)
{
struct efx_dma_priv *priv = platform_get_drvdata(pdev);
struct efx_dma_chan *dchan;
struct device_node *node = pdev->dev.of_node;
struct device_node *child = pdev->dev.of_node;
size_t i = 0;
int ret;
priv->dchan = devm_kzalloc(&pdev->dev, sizeof(struct efx_dma_chan) * priv->chan_count, GFP_KERNEL);
if (!priv->dchan) {
dev_err(&pdev->dev, "Failed to allocated memory for DMA channels\n");
return -ENOMEM;
}
for_each_child_of_node(node, child) {
dchan = &priv->dchan[i];
if (of_property_read_string(child, "dma-names", &dchan->name)) {
dev_warn(&pdev->dev, "Failed to get dma-names for channel %zu\n", i);
return -EINVAL;
}
dev_info(&pdev->dev, "Initialize DMA channel %zu for %s\n", i, dchan->name);
dchan->priv = priv;
vchan_init(&dchan->vchan, &priv->dma_dev);
dchan->chan_id = i;
dchan->reg = priv->base + i * 0x80;
dchan->cfg = NULL;
dchan->vchan.desc_free = &efx_dma_desc_free;
dchan->irq = irq_of_parse_and_map(node, i);
if (dchan->irq <= 0) {
dev_warn(&pdev->dev, "Failed to get interrupt number\n");
}
ret = devm_request_irq(&pdev->dev, dchan->irq, efx_dma_interrupt_handler,
IRQF_SHARED, dchan->name, dchan);
if (ret) {
dev_warn(&pdev->dev, "Warning: Failed to register interrupt handler\n");
}
efx_dma_stop_channel(&dchan->vchan.chan);
efx_dma_interrupt_pending_clear(&dchan->vchan.chan, 0xFFFFFFFF);
ret = of_property_read_u32(child, "chan-priority", &dchan->priority);
if (ret) {
dev_warn(&pdev->dev, "'chan-priority' is not found in the DMA device tree node. Use the default priority\n");
dchan->priority = 0;
}
dev_info(&pdev->dev, " channel address %px, priority %u\n", dchan->reg, dchan->priority);
efx_dma_set_channel_priority(dchan);
// initialize dchan->lock
spin_lock_init(&dchan->lock);
INIT_LIST_HEAD(&dchan->pending_list);
i++;
}
return 0;
}
static struct dma_chan *of_dma_efx_dma_xlate(struct of_phandle_args *dma_spec,
struct of_dma *ofdma)
{
struct efx_dma_priv *priv = ofdma->of_dma_data;
struct efx_dma_chan *dchan;
struct dma_chan *chan = NULL;
size_t chan_id;
chan_id = dma_spec->args[0];
if (chan_id < 0 || chan_id > priv->chan_count)
return NULL;
dchan = &priv->dchan[chan_id];
chan = dma_get_slave_channel(&dchan->vchan.chan);
if (!chan) {
dev_err(priv->dev, "Failed to get DMA slave channel %zu\n", chan_id);
return NULL;
}
dev_info(priv->dev, "Found DMA slave '%s' at channel %zu\n", dchan->name, chan_id);
return chan;
}
static int efx_dma_probe(struct platform_device *pdev)
{
struct efx_dma_priv *priv;
struct resource *res;
struct device_node *node = pdev->dev.of_node;
struct device_node *child = pdev->dev.of_node;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
dma_cap_zero(priv->dma_dev.cap_mask);
dma_cap_set(DMA_SLAVE, priv->dma_dev.cap_mask);
dma_cap_set(DMA_CYCLIC, priv->dma_dev.cap_mask);
dma_cap_set(DMA_MEMCPY, priv->dma_dev.cap_mask);
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
dev_warn(&pdev->dev, "Failed to set 64-bit DMA mask, falling back to 32-bit\n");
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "Failed to set 32-bit DMA mask\n");
return ret;
}
}
priv->dma_dev.src_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES;
priv->dma_dev.dst_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES;
priv->dma_dev.max_burst = 16;
priv->dma_dev.directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_MEM);
INIT_LIST_HEAD(&priv->dma_dev.channels);
// Initialize and register the DMA engine
priv->dma_dev.dev = &pdev->dev;
priv->dma_dev.device_alloc_chan_resources = efx_dma_alloc_chan_resources;
priv->dma_dev.device_free_chan_resources = efx_dma_free_chan_resources;
priv->dma_dev.device_prep_slave_sg = efx_dma_prep_slave_sg;
priv->dma_dev.device_prep_dma_cyclic = efx_dma_prep_cyclic;
priv->dma_dev.device_issue_pending = efx_dma_issue_pending;
priv->dma_dev.device_config = efx_dma_device_config;
priv->dma_dev.device_tx_status = efx_dma_tx_status;
priv->dma_dev.device_prep_dma_memcpy = efx_dma_prep_dma_memcpy;
priv->dma_dev.device_release = efx_dma_release;
priv->dma_dev.device_terminate_all = efx_dma_terminate_all;
platform_set_drvdata(pdev, priv);
priv->chan_count = 0;
ret = of_property_read_u32(node, "dma-channels", &priv->chan_count);
if (ret) {
dev_warn(&pdev->dev, "`dma-channels` not found in the DMA device tree node. Try to auto detect the number of channels\n");
for_each_child_of_node(node, child) {
priv->chan_count++;
}
}
dev_info(&pdev->dev, "Found %u DMA channels\n", priv->chan_count);
// Initialize DMA channels
ret = efx_dma_chan_probe(pdev);
if (ret)
return ret;
dev_info(&pdev->dev, "Register DMA controller\n");
ret = dma_async_device_register(&priv->dma_dev);
if (ret) {
dev_err(&pdev->dev, "Failed to register DMA controller\n");
return ret;
}
// Register DMA controller with device tree framework
ret = of_dma_controller_register(node, of_dma_efx_dma_xlate, priv);
if (ret < 0) {
dev_err(&pdev->dev, "Unable to register DMA controller to DT\n");
dma_async_device_unregister(&priv->dma_dev);
return ret;
}
dev_info(&pdev->dev, "DMA controller registered\n");
return 0;
}
static int efx_dma_remove(struct platform_device *pdev)
{
struct efx_dma_priv *priv = platform_get_drvdata(pdev);
dma_async_device_unregister(&priv->dma_dev);
return 0;
}
static const struct of_device_id efx_dma_of_match[] = {
{ .compatible = "efx,dma-controller" },
{ },
};
MODULE_DEVICE_TABLE(of, efx_dma_of_match);
static struct platform_driver efx_dma_driver = {
.probe = efx_dma_probe,
.remove = efx_dma_remove,
.driver = {
.name = "efx-dma",
.of_match_table = efx_dma_of_match,
},
};
module_platform_driver(efx_dma_driver);
MODULE_AUTHOR("Alim Hussin <mnalim@efinixinc.com>");
MODULE_DESCRIPTION("Efinix DMA driver");
MODULE_LICENSE("GPL v2");

View File

@@ -1109,3 +1109,29 @@ config MMC_SDHCI_EFX
config MMC_SDHCI_EXTERNAL_DMA
bool
config MMC_EFX_EMMC
tristate "Efinix eMMC host controller support"
depends on OF
help
This selects support for the Efinix eMMC Host Controller.
The controller supports eMMC 5.1 specification with HS200 and HS400 modes.
It includes hardware reset support and is designed for embedded applications.
If you have an Efinix platform with an eMMC device, say Y here.
If unsure, say N.
config MMC_EFX_SDIO
tristate "Efinix SDIO host controller support"
depends on OF
help
This selects support for the Efinix SDIO Host Controller.
The current controller support USH bus speed mode SDR25, DDR50 and SDR104.
By default, driver only support SDR25. To enable DDR50 and SDR104, add
sd-uhs-ddr50 or sd-uhs-sdr104 in DTS.
It includes hardware reset support and is designed for embedded applications.
Say M here to build the driver as a module.
Say N to exclude it.

View File

@@ -106,6 +106,10 @@ obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o
obj-$(CONFIG_MMC_CQHCI) += cqhci.o
obj-$(CONFIG_MMC_HSQ) += mmc_hsq.o
obj-$(CONFIG_MMC_SDHCI_EFX) += sdhci-efx.o
obj-$(CONFIG_MMC_EFX_EMMC) += efx-emmc.o
efx-emmc-y += efx_emmc_core.o efx_emmc_platform.o efx_emmc_dma.o efx_emmc_tuning.o
obj-$(CONFIG_MMC_EFX_SDIO) += efx-sdio.o
efx-sdio-y += efx_sdio_platform.o efx_sdio_core.o efx_sdio_dma.o efx_sdio_tuning.o
ifeq ($(CONFIG_CB710_DEBUG),y)
CFLAGS-cb710-mmc += -DDEBUG

304
drivers/mmc/host/efx_emmc.h Normal file
View File

@@ -0,0 +1,304 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Efinix eMMC Host Controller Driver Header with DMA Support
*
* Copyright (C) 2025 Efinix, Inc.
* Author: Teoh Choon Zone <czteoh@efinixinc.com>
*/
#ifndef __EFX_EMMC_H__
#define __EFX_EMMC_H__
#include <linux/types.h>
#include <linux/mmc/host.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#ifndef min3
#define min3(x, y, z) min(min(x, y), z)
#endif
#define EFX_ADMA_DESC_VALID BIT(0)
#define EFX_ADMA_DESC_END BIT(1)
#define EFX_ADMA_DESC_INT BIT(2)
#define EFX_ADMA_DESC_NOP (0 << 4)
#define EFX_ADMA_DESC_TRAN (2 << 4)
#define EFX_ADMA_DESC_LINK (3 << 4)
#define EFX_ADMA_MAX_LEN 65536
#define EFX_ADMA_DESC_ALIGN 8
#define EFX_ADMA_TABLE_SZ (512 * 8)
#define EFX_DMA_BOUNDARY_4K 0
#define EFX_DMA_BOUNDARY_8K 1
#define EFX_DMA_BOUNDARY_16K 2
#define EFX_DMA_BOUNDARY_32K 3
#define EFX_DMA_BOUNDARY_64K 4
#define EFX_DMA_BOUNDARY_128K 5
#define EFX_DMA_BOUNDARY_256K 6
#define EFX_DMA_BOUNDARY_512K 7
#define EFX_EMMC_VERSION 0x000
#define EFX_EMMC_BASE_REG0 0x004
#define EFX_EMMC_BASE_STATUS_REG0 0x008
#define EFX_EMMC_BASE_REG1 0x00C
#define EFX_EMMC_ARG2 0x100
#define EFX_EMMC_BLOCK_SIZE 0x104
#define EFX_EMMC_ARG1 0x108
#define EFX_EMMC_TRANSFER_MODE 0x10C
#define EFX_EMMC_RESPONSE0 0x110
#define EFX_EMMC_RESPONSE1 0x114
#define EFX_EMMC_RESPONSE2 0x118
#define EFX_EMMC_RESPONSE3 0x11C
#define EFX_EMMC_BUFFER_DATA_PORT 0x120
#define EFX_EMMC_PRESENT_STATE 0x124
#define EFX_EMMC_HOST_CONTROL 0x128
#define EFX_EMMC_INT_STATUS 0x130
#define EFX_EMMC_INT_STATUS_EN 0x134
#define EFX_EMMC_INT_SIGNAL_EN 0x138
#define EFX_EMMC_HOST_CAPABILITIES 0x140
#define EFX_EMMC_ADMA_SYS_ADDR_LOW 0x158
#define EFX_EMMC_ADMA_SYS_ADDR_HIGH 0x15C
#define EFX_SYS_DATE_REG 0x000
#define EFX_SYS_TEST_REG 0x004
#define EFX_SYS_RESET_REG 0x008
#define EFX_EMMC_BASE_REG0_CLK_EN BIT(16)
#define EFX_EMMC_BASE_REG0_CLK_DIV_MASK 0xFFFF
#define EFX_EMMC_BASE_STATUS_DAT_BUSY BIT(1)
#define EFX_EMMC_BASE_STATUS_CMD_BUSY BIT(0)
#define EFX_EMMC_BASE_REG1_SAMPLE_CNT_SHIFT 16
#define EFX_EMMC_BASE_REG1_SAMPLE_CNT_MASK (0xFFFF << 16)
#define EFX_EMMC_BASE_REG1_PHASE_SHIFT 6
#define EFX_EMMC_BASE_REG1_PHASE_MASK (0x7 << 6)
#define EFX_EMMC_BASE_REG1_PHASE_PULSE BIT(0)
#define EFX_EMMC_BLOCK_COUNT_SHIFT 16
#define EFX_EMMC_BLOCK_COUNT_MASK (0xFFFF << 16)
#define EFX_EMMC_BLOCK_SIZE_MASK 0xFFF
#define EFX_EMMC_DMA_BOUNDARY_SHIFT 12
#define EFX_EMMC_DMA_BOUNDARY_MASK (0x7 << 12)
#define EFX_EMMC_CMD_INDEX_SHIFT 24
#define EFX_EMMC_CMD_INDEX_MASK (0x3F << 24)
#define EFX_EMMC_DATA_PRESENT BIT(21)
#define EFX_EMMC_CMD_INDEX_CHECK_EN BIT(20)
#define EFX_EMMC_CMD_CRC_CHECK_EN BIT(19)
#define EFX_EMMC_RESP_TYPE_SHIFT 16
#define EFX_EMMC_RESP_TYPE_MASK (0x3 << 16)
#define EFX_EMMC_RESP_TYPE_NONE 0
#define EFX_EMMC_RESP_TYPE_136 1
#define EFX_EMMC_RESP_TYPE_48 2
#define EFX_EMMC_RESP_TYPE_48_BUSY 3
#define EFX_EMMC_MULTI_BLOCK_SEL BIT(5)
#define EFX_EMMC_DATA_XFER_DIR BIT(4)
#define EFX_EMMC_AUTO_CMD_EN_SHIFT 2
#define EFX_EMMC_AUTO_CMD_EN_MASK (0x3 << 2)
#define EFX_EMMC_BLOCK_COUNT_EN BIT(1)
#define EFX_EMMC_DMA_EN BIT(0)
#define EFX_EMMC_BUFFER_READ_EN BIT(11)
#define EFX_EMMC_BUFFER_WRITE_EN BIT(10)
#define EFX_EMMC_READ_XFER_ACTIVE BIT(9)
#define EFX_EMMC_WRITE_XFER_ACTIVE BIT(8)
#define EFX_EMMC_DAT_LINE_ACTIVE BIT(2)
#define EFX_EMMC_CMD_INHIBIT_DAT BIT(1)
#define EFX_EMMC_CMD_INHIBIT_CMD BIT(0)
#define EFX_EMMC_DATA_SAMPLING_MODE BIT(3)
#define EFX_EMMC_DATA_WIDTH_SHIFT 1
#define EFX_EMMC_DATA_WIDTH_MASK (0x3 << 1)
#define EFX_EMMC_DATA_WIDTH_1BIT 0
#define EFX_EMMC_DATA_WIDTH_4BIT 1
#define EFX_EMMC_DATA_WIDTH_8BIT 2
#define EFX_EMMC_INT_ADMA_ERROR BIT(25)
#define EFX_EMMC_INT_DATA_TIMEOUT_ERR BIT(22)
#define EFX_EMMC_INT_DATA_CRC_ERR BIT(21)
#define EFX_EMMC_INT_DATA_END_BIT_ERR BIT(20)
#define EFX_EMMC_INT_CMD_INDEX_ERR BIT(19)
#define EFX_EMMC_INT_CMD_END_BIT_ERR BIT(18)
#define EFX_EMMC_INT_CMD_CRC_ERR BIT(17)
#define EFX_EMMC_INT_CMD_TIMEOUT_ERR BIT(16)
#define EFX_EMMC_INT_BUFFER_READ_RDY BIT(5)
#define EFX_EMMC_INT_BUFFER_WRITE_RDY BIT(4)
#define EFX_EMMC_INT_BLOCK_GAP_EVENT BIT(2)
#define EFX_EMMC_INT_XFER_COMPLETE BIT(1)
#define EFX_EMMC_INT_CMD_COMPLETE BIT(0)
#define EFX_EMMC_INT_ERROR_MASK (EFX_EMMC_INT_ADMA_ERROR | \
EFX_EMMC_INT_DATA_TIMEOUT_ERR | \
EFX_EMMC_INT_DATA_CRC_ERR | \
EFX_EMMC_INT_DATA_END_BIT_ERR | \
EFX_EMMC_INT_CMD_INDEX_ERR | \
EFX_EMMC_INT_CMD_END_BIT_ERR | \
EFX_EMMC_INT_CMD_CRC_ERR | \
EFX_EMMC_INT_CMD_TIMEOUT_ERR)
#define EFX_EMMC_INT_ALL_MASK (EFX_EMMC_INT_ERROR_MASK | \
EFX_EMMC_INT_BUFFER_READ_RDY | \
EFX_EMMC_INT_BUFFER_WRITE_RDY | \
EFX_EMMC_INT_BLOCK_GAP_EVENT | \
EFX_EMMC_INT_XFER_COMPLETE | \
EFX_EMMC_INT_CMD_COMPLETE)
#define EFX_SYS_RESET_EMMC_DEV BIT(1)
#define EFX_SYS_RESET_EMMC_IP BIT(0)
#define EFX_EMMC_BASE_CLK_FREQ_MHZ 200
#define EFX_EMMC_MAX_BLOCK_LENGTH 512
#define EFX_EMMC_TIMEOUT_CLK_FREQ 200000000
#define EFX_EMMC_MIN_FREQ 400000
#define EFX_EMMC_MAX_FREQ 200000000
#define EFX_EMMC_PIO_TIMEOUT_MS 1000
#define EFX_EMMC_CLOCK_STABILIZE_DELAY 1000
#define EFX_EMMC_RESET_PULSE_WIDTH 1
#define EFX_EMMC_POST_RESET_DELAY 200
#define EFX_EMMC_CMD_RETRY_COUNT 3
#define EFX_EMMC_MAX_PLL_SHIFT 8
#define EFX_EMMC_PLL_SETTLING_TIME 50
#define EFX_EMMC_TUNING_TIMEOUT_MS 50
#define EFX_EMMC_TUNING_POLL_INTERVAL 200
#define EFX_EMMC_TUNING_BLOCK_SIZE_4BIT 64
#define EFX_EMMC_TUNING_BLOCK_SIZE_8BIT 128
#define EFX_EMMC_MIN_TIMING_MARGIN 1
struct efx_adma_desc {
u16 attr;
u16 len;
u32 addr;
} __packed __aligned(8);
struct efx_emmc_host {
struct mmc_host *mmc;
void __iomem *ioaddr;
void __iomem *sys_ioaddr;
struct clk *clk;
int irq;
struct mmc_request *mrq;
struct mmc_request *mrq_done;
struct mmc_command *cmd;
struct mmc_data *data;
u32 base_clk;
u32 current_clk;
int bytes_to_transfer;
int blocks_done;
unsigned int sg_offset;
struct efx_adma_desc *adma_desc;
dma_addr_t adma_desc_dma;
size_t adma_desc_sz;
void *bounce_buffer;
dma_addr_t bounce_dma;
unsigned int bounce_buffer_size;
unsigned int bounce_used;
bool bounce_active;
bool use_dma;
bool dma_64bit;
u32 clk_div;
u32 hs200_sample_count;
u32 hs200_pll_shift;
u32 hs200_margin;
u32 hs400_sample_count;
u32 hs400_pll_shift;
u32 hs400_margin;
u32 hs400_default_sample;
u32 hs400_default_pll;
bool tuning_done;
bool tuning_in_progress;
unsigned int prev_timing;
bool hs400_retune_pending;
struct delayed_work hs400_retune_work;
unsigned long tuned_timing_modes;
spinlock_t lock;
struct platform_device *pdev;
};
#define efx_emmc_dbg_irq(host, fmt, ...) \
dev_dbg(&(host)->pdev->dev, fmt, ##__VA_ARGS__)
#define efx_emmc_dbg_pio(host, fmt, ...) \
dev_dbg(&(host)->pdev->dev, fmt, ##__VA_ARGS__)
#define efx_emmc_dbg_cmd(host, fmt, ...) \
dev_dbg(&(host)->pdev->dev, fmt, ##__VA_ARGS__)
int efx_emmc_probe(struct platform_device *pdev);
int efx_emmc_remove(struct platform_device *pdev);
int efx_emmc_init_hw(struct efx_emmc_host *host);
void efx_emmc_reset_hw(struct efx_emmc_host *host);
void efx_emmc_hs400_retune_work(struct work_struct *work);
void efx_emmc_request(struct mmc_host *mmc, struct mmc_request *mrq);
void efx_emmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
int efx_emmc_get_cd(struct mmc_host *mmc);
int efx_emmc_card_busy_wrapper(struct mmc_host *mmc);
int efx_emmc_get_ro(struct mmc_host *mmc);
irqreturn_t efx_emmc_irq(int irq, void *dev_id);
void efx_emmc_send_command(struct efx_emmc_host *host, struct mmc_command *cmd);
void efx_emmc_finish_request(struct efx_emmc_host *host,
struct mmc_request *mrq);
void efx_emmc_finish_command(struct efx_emmc_host *host);
void efx_emmc_finish_data(struct efx_emmc_host *host);
void efx_emmc_transfer_pio(struct efx_emmc_host *host);
void efx_emmc_set_clock(struct efx_emmc_host *host, unsigned int clock);
void efx_emmc_set_bus_width(struct efx_emmc_host *host, int width);
void efx_emmc_set_timing(struct efx_emmc_host *host, unsigned int timing);
bool efx_emmc_card_busy(struct efx_emmc_host *host);
int efx_emmc_adma_table_pre(struct efx_emmc_host *host,
struct mmc_data *data);
void efx_emmc_adma_table_post(struct efx_emmc_host *host,
struct mmc_data *data);
void efx_emmc_prepare_dma(struct efx_emmc_host *host, struct mmc_data *data);
void efx_emmc_cleanup_dma(struct efx_emmc_host *host, struct mmc_data *data);
void efx_emmc_set_adma_addr(struct efx_emmc_host *host, dma_addr_t addr);
int efx_emmc_execute_tuning(struct mmc_host *mmc, u32 opcode);
int efx_emmc_execute_tuning_command(struct efx_emmc_host *host,
int bus_width);
void efx_emmc_set_timing_config(struct efx_emmc_host *host,
u32 sample_count, u32 pll_shift);
int efx_emmc_find_optimal_timing(struct efx_emmc_host *host,
u8 result_map[][EFX_EMMC_MAX_PLL_SHIFT],
u32 max_sample_count);
static inline u32 efx_emmc_readl(struct efx_emmc_host *host, u32 reg)
{
return readl(host->ioaddr + reg);
}
static inline void efx_emmc_writel(struct efx_emmc_host *host, u32 val, u32 reg)
{
writel(val, host->ioaddr + reg);
}
static inline u32 efx_emmc_sys_readl(struct efx_emmc_host *host, u32 reg)
{
return readl(host->sys_ioaddr + reg);
}
static inline void efx_emmc_sys_writel(struct efx_emmc_host *host, u32 val,
u32 reg)
{
writel(val, host->sys_ioaddr + reg);
}
#endif

View File

@@ -0,0 +1,975 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Efinix eMMC Host Controller Core Operations
*
* Copyright (C) 2025 Efinix, Inc.
* Author: Teoh Choon Zone <czteoh@efinixinc.com>
*/
#include <linux/compiler.h>
#include <linux/delay.h>
#include <linux/scatterlist.h>
#include <linux/jiffies.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
#include "efx_emmc.h"
static void efx_emmc_complete_pending_request(struct efx_emmc_host *host)
{
struct mmc_request *mrq;
mrq = READ_ONCE(host->mrq_done);
if (!mrq)
return;
WRITE_ONCE(host->mrq_done, NULL);
mmc_request_done(host->mmc, mrq);
}
bool efx_emmc_card_busy(struct efx_emmc_host *host)
{
return !!(efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE) &
EFX_EMMC_DAT_LINE_ACTIVE);
}
static bool efx_emmc_need_stop_command(struct mmc_data *data,
struct mmc_request *mrq)
{
if (data->blocks <= 1)
return false;
if (!data->stop)
return false;
if (mrq->sbc)
return false;
return true;
}
void efx_emmc_finish_request(struct efx_emmc_host *host,
struct mmc_request *mrq)
{
if (host->data) {
efx_emmc_cleanup_dma(host, host->data);
}
host->mrq = NULL;
host->cmd = NULL;
host->data = NULL;
host->bytes_to_transfer = 0;
host->blocks_done = 0;
host->sg_offset = 0;
host->use_dma = false;
if (mrq)
WRITE_ONCE(host->mrq_done, mrq);
}
static void efx_emmc_transfer_pio_read(struct efx_emmc_host *host)
{
struct mmc_data *data;
struct scatterlist *sg;
u32 *buf;
int words_in_fifo, words_transferred, i;
unsigned int sg_offset, remaining_in_sg, words_to_transfer;
u32 present_state;
data = host->data;
if (!data) {
return;
}
sg = data->sg;
if (!sg) {
dev_err(&host->pdev->dev, "No scatter-gather list for read\n");
return;
}
words_in_fifo = data->blksz / sizeof(u32);
sg_offset = host->sg_offset;
words_transferred = 0;
while (words_transferred < words_in_fifo &&
host->bytes_to_transfer > 0) {
while (sg && sg_offset >= sg->length) {
sg_offset -= sg->length;
sg = sg_next(sg);
}
if (!sg) {
dev_err(&host->pdev->dev,
"No more scatter-gather entries\n");
break;
}
buf = (u32 *)(sg_virt(sg) + sg_offset);
remaining_in_sg = sg->length - sg_offset;
words_to_transfer = min3(remaining_in_sg / sizeof(u32),
(unsigned int)(words_in_fifo -
words_transferred),
(unsigned int)(host->bytes_to_transfer /
sizeof(u32)));
if (words_to_transfer == 0) {
break;
}
for (i = 0; i < words_to_transfer; i++) {
buf[i] = efx_emmc_readl(host, EFX_EMMC_BUFFER_DATA_PORT);
}
words_transferred += words_to_transfer;
sg_offset += words_to_transfer * 4;
host->bytes_to_transfer -= words_to_transfer * 4;
efx_emmc_dbg_pio(host,
"PIO read: %d words, %d bytes remaining\n",
words_to_transfer, host->bytes_to_transfer);
}
host->sg_offset += words_transferred * 4;
host->blocks_done = (data->blksz * data->blocks -
host->bytes_to_transfer) / data->blksz;
efx_emmc_dbg_pio(host,
"PIO read completed: %d words total, "
"%d blocks done, %d bytes remaining\n",
words_transferred, host->blocks_done,
host->bytes_to_transfer);
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
efx_emmc_dbg_pio(host, "Present state after PIO read: 0x%08x\n",
present_state);
}
static void efx_emmc_transfer_pio_write(struct efx_emmc_host *host)
{
struct mmc_data *data;
struct scatterlist *sg;
u32 *buf;
int words_in_fifo, words_transferred, i;
unsigned int sg_offset, remaining_in_sg, words_to_transfer;
u32 present_state;
data = host->data;
if (!data) {
return;
}
sg = data->sg;
if (!sg) {
dev_err(&host->pdev->dev, "No scatter-gather list for write\n");
return;
}
words_in_fifo = data->blksz / sizeof(u32);
sg_offset = host->sg_offset;
words_transferred = 0;
while (words_transferred < words_in_fifo &&
host->bytes_to_transfer > 0) {
while (sg && sg_offset >= sg->length) {
sg_offset -= sg->length;
sg = sg_next(sg);
}
if (!sg) {
dev_err(&host->pdev->dev,
"No more scatter-gather entries\n");
break;
}
buf = (u32 *)(sg_virt(sg) + sg_offset);
remaining_in_sg = sg->length - sg_offset;
words_to_transfer = min3(remaining_in_sg / sizeof(u32),
(unsigned int)(words_in_fifo -
words_transferred),
(unsigned int)(host->bytes_to_transfer /
sizeof(u32)));
if (words_to_transfer == 0) {
break;
}
for (i = 0; i < words_to_transfer; i++) {
efx_emmc_writel(host, buf[i], EFX_EMMC_BUFFER_DATA_PORT);
}
words_transferred += words_to_transfer;
sg_offset += words_to_transfer * 4;
host->bytes_to_transfer -= words_to_transfer * 4;
efx_emmc_dbg_pio(host,
"PIO write: %d words, %d bytes remaining\n",
words_to_transfer, host->bytes_to_transfer);
}
host->sg_offset += words_transferred * 4;
host->blocks_done = (data->blksz * data->blocks -
host->bytes_to_transfer) / data->blksz;
efx_emmc_dbg_pio(host,
"PIO write completed: %d words total, %d blocks done, "
"%d bytes remaining\\n",
words_transferred, host->blocks_done,
host->bytes_to_transfer);
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
efx_emmc_dbg_pio(host, "Present state after PIO write: 0x%08x\n",
present_state);
}
void efx_emmc_transfer_pio(struct efx_emmc_host *host)
{
struct mmc_data *data;
u32 present_state;
data = host->data;
if (!data) {
return;
}
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
if (data->flags & MMC_DATA_READ) {
if (present_state & EFX_EMMC_BUFFER_READ_EN) {
efx_emmc_transfer_pio_read(host);
}
} else {
if (present_state & EFX_EMMC_BUFFER_WRITE_EN) {
efx_emmc_transfer_pio_write(host);
}
}
}
void efx_emmc_finish_data(struct efx_emmc_host *host)
{
struct mmc_data *data;
u32 present_state;
int retry_count;
data = host->data;
if (!data) {
return;
}
if (data->error == 0) {
data->bytes_xfered = data->blksz * data->blocks;
efx_emmc_dbg_cmd(host,
"Data transfer completed: %d bytes (DMA: %s)\n",
data->bytes_xfered,
host->use_dma ? "enabled" : "disabled");
} else {
data->bytes_xfered = 0;
dev_err(&host->pdev->dev,
"Data transfer failed with error %d\n",
data->error);
}
efx_emmc_cleanup_dma(host, data);
host->data = NULL;
host->bytes_to_transfer = 0;
host->blocks_done = 0;
host->sg_offset = 0;
if (efx_emmc_need_stop_command(data, host->mrq) && !data->error) {
retry_count = 0;
while (retry_count < 100) {
present_state = efx_emmc_readl(host,
EFX_EMMC_PRESENT_STATE);
if (!(present_state & (EFX_EMMC_DAT_LINE_ACTIVE |
EFX_EMMC_READ_XFER_ACTIVE |
EFX_EMMC_WRITE_XFER_ACTIVE))) {
break;
}
udelay(10);
retry_count++;
}
if (retry_count >= 100) {
dev_warn(&host->pdev->dev,
"Data lines still active before CMD12, proceeding anyway\n");
}
udelay(100);
efx_emmc_dbg_cmd(host,
"Sending CMD12 (STOP) for %d-block transfer\n",
data->blocks);
host->cmd = data->stop;
efx_emmc_send_command(host, data->stop);
} else {
efx_emmc_dbg_cmd(host, "Finishing request\n");
if (host->mrq) {
efx_emmc_finish_request(host, host->mrq);
}
}
}
void efx_emmc_finish_command(struct efx_emmc_host *host)
{
struct mmc_command *cmd;
u32 resp[4];
u32 present_state;
cmd = host->cmd;
if (!cmd) {
return;
}
if (cmd->flags & MMC_RSP_PRESENT) {
if (cmd->flags & MMC_RSP_136) {
resp[0] = efx_emmc_readl(host, EFX_EMMC_RESPONSE0);
resp[1] = efx_emmc_readl(host, EFX_EMMC_RESPONSE1);
resp[2] = efx_emmc_readl(host, EFX_EMMC_RESPONSE2);
resp[3] = efx_emmc_readl(host, EFX_EMMC_RESPONSE3) & 0xFFFFFF;
cmd->resp[0] = resp[3] << 8 | resp[2] >> 24;
cmd->resp[1] = resp[2] << 8 | resp[1] >> 24;
cmd->resp[2] = resp[1] << 8 | resp[0] >> 24;
cmd->resp[3] = resp[0] << 8;
efx_emmc_dbg_cmd(host,
"CMD%d 136-bit response: %08x %08x %08x %08x\n",
cmd->opcode, cmd->resp[0], cmd->resp[1],
cmd->resp[2], cmd->resp[3]);
} else {
cmd->resp[0] = efx_emmc_readl(host, EFX_EMMC_RESPONSE0);
efx_emmc_dbg_cmd(host, "CMD%d response: 0x%08x\n",
cmd->opcode, cmd->resp[0]);
}
}
if (cmd->opcode == 6 && cmd->error == 0) {
u32 arg = cmd->arg;
u8 index = (arg >> 16) & 0xFF;
u8 value = (arg >> 8) & 0xFF;
if (index == 185) {
switch (value) {
case 0:
case 1:
dev_dbg(&host->pdev->dev,
"CMD6 mode switch to legacy/HS completed\n");
efx_emmc_set_timing_config(host, 0, 0);
break;
case 2:
dev_dbg(&host->pdev->dev,
"CMD6 mode switch to DDR52 completed\n");
efx_emmc_set_timing_config(host, 2, 2);
break;
case 3:
dev_dbg(&host->pdev->dev,
"CMD6 mode switch to HS200 completed\n");
break;
default:
efx_emmc_dbg_cmd(host,
"CMD6 mode switch to timing value %u\n",
value);
break;
}
efx_emmc_writel(host, 0xFFFFFFFF, EFX_EMMC_INT_STATUS);
}
}
if (cmd->opcode == 23 && host->mrq && host->mrq->cmd) {
efx_emmc_dbg_cmd(host,
"CMD23 complete, sending main command CMD%d\n",
host->mrq->cmd->opcode);
host->cmd = host->mrq->cmd;
efx_emmc_send_command(host, host->mrq->cmd);
return;
}
host->cmd = NULL;
if (!host->data) {
efx_emmc_dbg_cmd(host, "Command complete, finishing request\n");
if (host->mrq) {
efx_emmc_finish_request(host, host->mrq);
}
} else {
efx_emmc_dbg_cmd(host,
"Command complete, data transfer continues (DMA: %s)\n",
host->use_dma ? "enabled" : "disabled");
if (!host->use_dma) {
bool buffer_ready_read, buffer_ready_write;
present_state = efx_emmc_readl(host,
EFX_EMMC_PRESENT_STATE);
efx_emmc_dbg_cmd(host,
"Present state after command: 0x%08x\n",
present_state);
buffer_ready_read = !!(present_state & EFX_EMMC_BUFFER_READ_EN);
buffer_ready_write = !!(present_state & EFX_EMMC_BUFFER_WRITE_EN);
if (host->data->flags & MMC_DATA_READ) {
if (buffer_ready_read) {
efx_emmc_dbg_pio(host,
"Buffer immediately ready for read, starting PIO\n");
efx_emmc_transfer_pio(host);
}
} else {
if (buffer_ready_write) {
efx_emmc_dbg_pio(host,
"Buffer immediately ready for write, starting PIO\n");
efx_emmc_transfer_pio(host);
}
}
}
}
}
void efx_emmc_send_command(struct efx_emmc_host *host, struct mmc_command *cmd)
{
u32 command, present_state;
unsigned long timeout;
struct mmc_data *data;
u16 cmd_timeout;
data = cmd->data;
host->cmd = cmd;
efx_emmc_dbg_cmd(host, "Sending CMD%d, arg=0x%08x%s\n",
cmd->opcode, cmd->arg, (cmd->opcode == 12) ? " (STOP)" : "");
if (cmd->opcode == 6) {
u32 arg = cmd->arg;
u8 index = (arg >> 16) & 0xFF;
u8 value = (arg >> 8) & 0xFF;
if (index == 185) {
dev_dbg(&host->pdev->dev,
"CMD6 mode switch detected (index=%u, value=%u)\n",
index, value);
efx_emmc_set_timing_config(host, 0, 0);
msleep(10);
efx_emmc_writel(host, 0xFFFFFFFF, EFX_EMMC_INT_STATUS);
}
}
switch (cmd->opcode) {
case 6:
cmd_timeout = 2000;
break;
case 12:
cmd_timeout = 1000;
break;
case 23:
cmd_timeout = 500;
break;
default:
cmd_timeout = 500;
break;
}
timeout = jiffies + msecs_to_jiffies(cmd_timeout);
while (time_before(jiffies, timeout)) {
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
if (!(present_state & EFX_EMMC_CMD_INHIBIT_CMD)) {
if (!data || !(present_state & EFX_EMMC_CMD_INHIBIT_DAT)) {
break;
}
}
cpu_relax();
}
if (time_after_eq(jiffies, timeout)) {
dev_err(&host->pdev->dev,
"Command line timeout, present_state=0x%08x\n",
efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE));
cmd->error = -ETIMEDOUT;
if (host->mrq) {
efx_emmc_finish_request(host, host->mrq);
}
return;
}
efx_emmc_writel(host, cmd->arg, EFX_EMMC_ARG1);
command = (cmd->opcode << EFX_EMMC_CMD_INDEX_SHIFT) &
EFX_EMMC_CMD_INDEX_MASK;
if (cmd->flags & MMC_RSP_PRESENT) {
if (cmd->flags & MMC_RSP_136) {
command |= (EFX_EMMC_RESP_TYPE_136 << EFX_EMMC_RESP_TYPE_SHIFT);
} else if (cmd->flags & MMC_RSP_BUSY) {
command |= (EFX_EMMC_RESP_TYPE_48_BUSY << EFX_EMMC_RESP_TYPE_SHIFT);
} else {
command |= (EFX_EMMC_RESP_TYPE_48 << EFX_EMMC_RESP_TYPE_SHIFT);
}
if (cmd->flags & MMC_RSP_CRC) {
command |= EFX_EMMC_CMD_CRC_CHECK_EN;
}
if (cmd->flags & MMC_RSP_OPCODE) {
command |= EFX_EMMC_CMD_INDEX_CHECK_EN;
}
}
if (data) {
command |= EFX_EMMC_DATA_PRESENT;
host->use_dma = (data->blksz * data->blocks >= 512);
if (host->use_dma) {
efx_emmc_prepare_dma(host, data);
}
efx_emmc_writel(host, (data->blocks << EFX_EMMC_BLOCK_COUNT_SHIFT) |
(data->blksz & EFX_EMMC_BLOCK_SIZE_MASK),
EFX_EMMC_BLOCK_SIZE);
if (data->blocks > 1) {
command |= EFX_EMMC_MULTI_BLOCK_SEL;
command |= EFX_EMMC_BLOCK_COUNT_EN;
if (host->mrq && host->mrq->sbc) {
efx_emmc_dbg_cmd(host,
"Multi-block transfer: CMD23 used, no CMD12 needed\n");
} else {
efx_emmc_dbg_cmd(host,
"Multi-block transfer: manual CMD12 will be used\n");
}
}
if (data->flags & MMC_DATA_READ) {
command |= EFX_EMMC_DATA_XFER_DIR;
}
if (host->use_dma) {
command |= EFX_EMMC_DMA_EN;
efx_emmc_dbg_cmd(host, "DMA enabled for data transfer\n");
} else {
host->bytes_to_transfer = data->blksz * data->blocks;
host->blocks_done = 0;
host->sg_offset = 0;
efx_emmc_dbg_cmd(host, "Using PIO for data transfer\n");
}
host->data = data;
efx_emmc_dbg_cmd(host, "Data transfer: %d blocks of %d bytes, %s, %s\n",
data->blocks, data->blksz,
(data->flags & MMC_DATA_READ) ? "read" : "write",
host->use_dma ? "DMA" : "PIO");
}
efx_emmc_dbg_cmd(host, "Command register: 0x%08x\n", command);
efx_emmc_writel(host, command, EFX_EMMC_TRANSFER_MODE);
}
void efx_emmc_set_clock(struct efx_emmc_host *host, unsigned int clock)
{
u32 div, reg;
unsigned long timeout;
if (clock == 0) {
reg = efx_emmc_readl(host, EFX_EMMC_BASE_REG0);
reg &= ~EFX_EMMC_BASE_REG0_CLK_EN;
efx_emmc_writel(host, reg, EFX_EMMC_BASE_REG0);
host->current_clk = 0;
return;
}
if (clock > EFX_EMMC_MAX_FREQ) {
clock = EFX_EMMC_MAX_FREQ;
}
if (clock >= host->base_clk) {
div = 1;
} else {
div = (host->base_clk + clock - 1) / clock;
if (div > 1 && (div & 1)) {
div += 1;
}
}
reg = efx_emmc_readl(host, EFX_EMMC_BASE_REG0);
reg &= ~EFX_EMMC_BASE_REG0_CLK_EN;
efx_emmc_writel(host, reg, EFX_EMMC_BASE_REG0);
reg = (reg & ~EFX_EMMC_BASE_REG0_CLK_DIV_MASK) |
(div & EFX_EMMC_BASE_REG0_CLK_DIV_MASK);
efx_emmc_writel(host, reg, EFX_EMMC_BASE_REG0);
reg |= EFX_EMMC_BASE_REG0_CLK_EN;
efx_emmc_writel(host, reg, EFX_EMMC_BASE_REG0);
if (clock <= 400000) {
usleep_range(500, 1000);
} else if (clock <= 25000000) {
usleep_range(100, 200);
} else {
usleep_range(50, 100);
}
timeout = jiffies + msecs_to_jiffies(50);
while (time_before(jiffies, timeout)) {
if (!(efx_emmc_readl(host, EFX_EMMC_BASE_STATUS_REG0) &
(EFX_EMMC_BASE_STATUS_CMD_BUSY |
EFX_EMMC_BASE_STATUS_DAT_BUSY))) {
break;
}
cpu_relax();
}
host->current_clk = host->base_clk / div;
host->clk_div = div;
dev_dbg(&host->pdev->dev, "Set clock to %u Hz (div=%u, actual=%u)\n",
clock, div, host->current_clk);
}
void efx_emmc_set_bus_width(struct efx_emmc_host *host, int width)
{
u32 reg;
reg = efx_emmc_readl(host, EFX_EMMC_HOST_CONTROL);
reg &= ~EFX_EMMC_DATA_WIDTH_MASK;
switch (width) {
case MMC_BUS_WIDTH_1:
reg |= (EFX_EMMC_DATA_WIDTH_1BIT << EFX_EMMC_DATA_WIDTH_SHIFT);
break;
case MMC_BUS_WIDTH_4:
reg |= (EFX_EMMC_DATA_WIDTH_4BIT << EFX_EMMC_DATA_WIDTH_SHIFT);
break;
case MMC_BUS_WIDTH_8:
reg |= (EFX_EMMC_DATA_WIDTH_8BIT << EFX_EMMC_DATA_WIDTH_SHIFT);
break;
default:
dev_warn(&host->pdev->dev, "Unsupported bus width: %d\n", width);
return;
}
efx_emmc_writel(host, reg, EFX_EMMC_HOST_CONTROL);
dev_dbg(&host->pdev->dev, "Set bus width to %d bits\n", width);
}
void efx_emmc_set_timing(struct efx_emmc_host *host, unsigned int timing)
{
u32 reg;
bool needs_tuning = false;
reg = efx_emmc_readl(host, EFX_EMMC_HOST_CONTROL);
switch (timing) {
case MMC_TIMING_LEGACY:
case MMC_TIMING_MMC_HS:
reg &= ~EFX_EMMC_DATA_SAMPLING_MODE;
break;
case MMC_TIMING_MMC_DDR52:
reg |= EFX_EMMC_DATA_SAMPLING_MODE;
host->tuning_done = true;
set_bit(timing, &host->tuned_timing_modes);
break;
case MMC_TIMING_MMC_HS200:
reg &= ~EFX_EMMC_DATA_SAMPLING_MODE;
if (!test_bit(timing, &host->tuned_timing_modes)) {
needs_tuning = true;
host->tuning_done = false;
dev_dbg(&host->pdev->dev, "HS200 mode - tuning required\n");
} else {
efx_emmc_set_timing_config(host, host->hs200_sample_count,
host->hs200_pll_shift);
dev_dbg(&host->pdev->dev,
"HS200 mode - already tuned, restored timing (sample=%u, pll=%u)\n",
host->hs200_sample_count, host->hs200_pll_shift);
}
break;
case MMC_TIMING_MMC_HS400:
reg |= EFX_EMMC_DATA_SAMPLING_MODE;
if (!test_bit(timing, &host->tuned_timing_modes)) {
u32 initial_sample = host->hs400_default_sample;
u32 initial_pll = host->hs400_default_pll;
efx_emmc_set_timing_config(host, initial_sample, initial_pll);
needs_tuning = true;
host->tuning_done = false;
host->hs400_retune_pending = true;
dev_info(&host->pdev->dev,
"HS400 mode - using default sample=%u pll=%u, clock=%u Hz\n",
initial_sample, initial_pll, host->mmc->ios.clock);
dev_info(&host->pdev->dev,
"HS400 tuning scheduled (clock=%u Hz)\n",
host->mmc->ios.clock);
schedule_delayed_work(&host->hs400_retune_work,
msecs_to_jiffies(10));
} else {
efx_emmc_set_timing_config(host, host->hs400_sample_count,
host->hs400_pll_shift);
dev_info(&host->pdev->dev,
"HS400 mode - already tuned, restored timing (sample=%u, pll=%u)\n",
host->hs400_sample_count, host->hs400_pll_shift);
}
break;
default:
dev_warn(&host->pdev->dev, "Unsupported timing: %d\n", timing);
return;
}
efx_emmc_writel(host, reg, EFX_EMMC_HOST_CONTROL);
dev_dbg(&host->pdev->dev, "Set timing mode: %s (%d) - %s\n",
timing == MMC_TIMING_LEGACY ? "Legacy" :
timing == MMC_TIMING_MMC_HS ? "High Speed" :
timing == MMC_TIMING_MMC_DDR52 ? "DDR52" :
timing == MMC_TIMING_MMC_HS200 ? "HS200" :
timing == MMC_TIMING_MMC_HS400 ? "HS400" : "Unknown", timing,
needs_tuning ? "NEEDS TUNING" : "NO TUNING NEEDED");
host->prev_timing = timing;
}
irqreturn_t efx_emmc_irq(int irq, void *dev_id)
{
struct efx_emmc_host *host;
u32 intstat, present_state;
irqreturn_t result;
host = dev_id;
result = IRQ_NONE;
spin_lock(&host->lock);
intstat = efx_emmc_readl(host, EFX_EMMC_INT_STATUS);
if (!intstat) {
goto out;
}
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
efx_emmc_dbg_irq(host, "IRQ: status=0x%08x, present=0x%08x\n",
intstat, present_state);
efx_emmc_writel(host, intstat, EFX_EMMC_INT_STATUS);
result = IRQ_HANDLED;
if (intstat & EFX_EMMC_INT_ERROR_MASK) {
if (intstat & EFX_EMMC_INT_ADMA_ERROR) {
dev_err(&host->pdev->dev, "ADMA error detected\n");
if (host->data) {
host->data->error = -EIO;
}
}
if (host->cmd && (host->cmd->opcode == 52 || host->cmd->opcode == 8 ||
host->cmd->opcode == 5 || host->cmd->opcode == 55)) {
efx_emmc_dbg_irq(host,
"Expected timeout for CMD%d during card detection\n",
host->cmd->opcode);
} else if (host->tuning_in_progress) {
efx_emmc_dbg_irq(host, "Tuning error (expected): 0x%08x\n",
(unsigned int)(intstat & EFX_EMMC_INT_ERROR_MASK));
} else {
dev_err(&host->pdev->dev, "Error interrupt: 0x%08x\n",
(unsigned int)(intstat & EFX_EMMC_INT_ERROR_MASK));
}
if (host->cmd) {
if (intstat & EFX_EMMC_INT_CMD_TIMEOUT_ERR) {
host->cmd->error = -ETIMEDOUT;
if (host->cmd->opcode == 12) {
dev_warn(&host->pdev->dev,
"CMD12 timeout - data may have completed normally\n");
host->cmd = NULL;
if (host->mrq) {
efx_emmc_finish_request(host, host->mrq);
}
goto out;
} else if (host->cmd->opcode == 6) {
dev_err(&host->pdev->dev,
"CMD6 (mode switch) timeout - arg=0x%08x\n",
host->cmd->arg);
} else if (!(host->cmd->opcode == 52 ||
host->cmd->opcode == 8 ||
host->cmd->opcode == 5 ||
host->cmd->opcode == 55)) {
dev_err(&host->pdev->dev, "CMD%d timeout\n",
host->cmd->opcode);
}
} else if (intstat & (EFX_EMMC_INT_CMD_CRC_ERR |
EFX_EMMC_INT_CMD_END_BIT_ERR |
EFX_EMMC_INT_CMD_INDEX_ERR)) {
host->cmd->error = -EILSEQ;
if (host->cmd->opcode == 6) {
dev_err(&host->pdev->dev,
"CMD6 (mode switch) CRC/protocol error - arg=0x%08x\n",
host->cmd->arg);
} else {
dev_err(&host->pdev->dev,
"CMD%d CRC/protocol error\n",
host->cmd->opcode);
}
}
}
if (host->data) {
if (intstat & EFX_EMMC_INT_DATA_TIMEOUT_ERR) {
host->data->error = -ETIMEDOUT;
dev_err(&host->pdev->dev, "Data timeout error\n");
}
if (intstat & EFX_EMMC_INT_DATA_CRC_ERR) {
host->data->error = -EILSEQ;
dev_err(&host->pdev->dev, "Data CRC error\n");
}
if (intstat & EFX_EMMC_INT_DATA_END_BIT_ERR) {
host->data->error = -EILSEQ;
dev_err(&host->pdev->dev, "Data end bit error\n");
}
}
if (!(host->cmd && host->cmd->opcode == 12 &&
(intstat & EFX_EMMC_INT_CMD_TIMEOUT_ERR))) {
if (host->mrq) {
efx_emmc_finish_request(host, host->mrq);
}
}
goto out;
}
if (intstat & EFX_EMMC_INT_CMD_COMPLETE) {
efx_emmc_dbg_irq(host, "Command complete\n");
efx_emmc_finish_command(host);
}
if (likely(!host->use_dma) &&
(intstat & (EFX_EMMC_INT_BUFFER_READ_RDY |
EFX_EMMC_INT_BUFFER_WRITE_RDY))) {
efx_emmc_dbg_irq(host, "Buffer ready for %s\n",
(intstat & EFX_EMMC_INT_BUFFER_READ_RDY) ? "read" : "write");
efx_emmc_transfer_pio(host);
if (host->data && host->bytes_to_transfer > 0) {
efx_emmc_dbg_pio(host,
"Waiting for more data: %d bytes remaining\n",
host->bytes_to_transfer);
} else if (host->data && host->bytes_to_transfer == 0) {
efx_emmc_dbg_pio(host,
"All data transferred via PIO, completing transfer\n");
efx_emmc_finish_data(host);
}
}
if (intstat & EFX_EMMC_INT_XFER_COMPLETE) {
efx_emmc_dbg_irq(host, "Transfer complete interrupt (DMA: %s)\n",
host->use_dma ? "enabled" : "disabled");
efx_emmc_finish_data(host);
}
if (intstat & ~(EFX_EMMC_INT_CMD_COMPLETE | EFX_EMMC_INT_BUFFER_READ_RDY |
EFX_EMMC_INT_BUFFER_WRITE_RDY |
EFX_EMMC_INT_XFER_COMPLETE | EFX_EMMC_INT_ERROR_MASK)) {
efx_emmc_dbg_irq(host, "Unhandled interrupt bits: 0x%08x\n",
(unsigned int)(intstat &
~(EFX_EMMC_INT_CMD_COMPLETE |
EFX_EMMC_INT_BUFFER_READ_RDY |
EFX_EMMC_INT_BUFFER_WRITE_RDY |
EFX_EMMC_INT_XFER_COMPLETE |
EFX_EMMC_INT_ERROR_MASK)));
}
out:
spin_unlock(&host->lock);
efx_emmc_complete_pending_request(host);
return result;
}
void efx_emmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct efx_emmc_host *host;
unsigned long flags;
host = mmc_priv(mmc);
spin_lock_irqsave(&host->lock, flags);
if (!host->clk) {
dev_err(&host->pdev->dev, "No clock available\n");
mrq->cmd->error = -ENODEV;
spin_unlock_irqrestore(&host->lock, flags);
mmc_request_done(mmc, mrq);
return;
}
efx_emmc_dbg_cmd(host, "New request: CMD%d\n", mrq->cmd->opcode);
host->mrq = mrq;
if (mrq->sbc) {
efx_emmc_dbg_cmd(host,
"Sending CMD23 (SET_BLOCK_COUNT) first, blocks=%u\n",
mrq->sbc->arg);
host->cmd = mrq->sbc;
efx_emmc_send_command(host, mrq->sbc);
} else {
efx_emmc_send_command(host, mrq->cmd);
}
spin_unlock_irqrestore(&host->lock, flags);
efx_emmc_complete_pending_request(host);
}
void efx_emmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct efx_emmc_host *host;
unsigned long flags;
bool need_host_control_update = false;
host = mmc_priv(mmc);
spin_lock_irqsave(&host->lock, flags);
if (ios->clock != host->current_clk) {
efx_emmc_set_clock(host, ios->clock);
}
if (ios->bus_width != MMC_BUS_WIDTH_1) {
efx_emmc_set_bus_width(host, ios->bus_width);
need_host_control_update = true;
}
if (ios->timing != host->prev_timing) {
dev_dbg(&host->pdev->dev, "Timing mode: %u -> %u, Clock: %u Hz\n",
host->prev_timing, ios->timing, ios->clock);
}
efx_emmc_set_timing(host, ios->timing);
spin_unlock_irqrestore(&host->lock, flags);
}
int efx_emmc_get_cd(struct mmc_host *mmc)
{
return 1;
}
int efx_emmc_card_busy_wrapper(struct mmc_host *mmc)
{
struct efx_emmc_host *host;
unsigned long flags;
bool busy;
host = mmc_priv(mmc);
spin_lock_irqsave(&host->lock, flags);
busy = efx_emmc_card_busy(host);
spin_unlock_irqrestore(&host->lock, flags);
return busy;
}
int efx_emmc_get_ro(struct mmc_host *mmc)
{
return 0;
}

View File

@@ -0,0 +1,209 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Efinix eMMC Host Controller DMA Support
*
* Copyright (C) 2025 Efinix, Inc.
* Author: Teoh Choon Zone <czteoh@efinixinc.com>
*/
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include "efx_emmc.h"
void efx_emmc_set_adma_addr(struct efx_emmc_host *host, dma_addr_t addr)
{
efx_emmc_writel(host, (u32)addr, EFX_EMMC_ADMA_SYS_ADDR_LOW);
efx_emmc_writel(host, 0, EFX_EMMC_ADMA_SYS_ADDR_HIGH);
}
static void efx_emmc_adma_mark_end(struct efx_adma_desc *desc)
{
desc->attr |= EFX_ADMA_DESC_END;
}
static void efx_emmc_adma_set_desc(struct efx_adma_desc *desc, u32 addr,
u16 len, u16 attr)
{
desc->attr = attr;
desc->len = len;
desc->addr = addr;
}
int efx_emmc_adma_table_pre(struct efx_emmc_host *host, struct mmc_data *data)
{
struct efx_adma_desc *desc;
struct scatterlist *sg;
dma_addr_t addr, align_addr;
u32 len, offset, align_len;
int i, desc_count = 0;
bool use_bounce = false;
for_each_sg(data->sg, sg, data->sg_len, i) {
addr = sg_dma_address(sg);
len = sg_dma_len(sg);
if ((addr & 0x7) || (len & 0x7)) {
use_bounce = true;
break;
}
}
if (use_bounce) {
if (!host->bounce_buffer) {
dev_err(&host->pdev->dev, "Bounce buffer not available\n");
return -ENOMEM;
}
if (data->blksz * data->blocks > host->bounce_buffer_size) {
dev_err(&host->pdev->dev, "Transfer too large for bounce buffer\n");
return -EINVAL;
}
if (data->flags & MMC_DATA_WRITE) {
struct scatterlist *sg;
char *bounce_pos;
int i;
bounce_pos = host->bounce_buffer;
for_each_sg(data->sg, sg, data->sg_len, i) {
memcpy(bounce_pos, sg_virt(sg), sg->length);
bounce_pos += sg->length;
}
}
desc = host->adma_desc;
efx_emmc_adma_set_desc(desc, host->bounce_dma,
data->blksz * data->blocks,
EFX_ADMA_DESC_VALID | EFX_ADMA_DESC_TRAN);
efx_emmc_adma_mark_end(desc);
desc_count = 1;
host->bounce_active = true;
host->bounce_used = data->blksz * data->blocks;
} else {
desc = host->adma_desc;
host->bounce_active = false;
host->bounce_used = 0;
for_each_sg(data->sg, sg, data->sg_len, i) {
addr = sg_dma_address(sg);
len = sg_dma_len(sg);
offset = 0;
while (len > 0) {
align_addr = addr + offset;
align_len = min(len, (u32)EFX_ADMA_MAX_LEN);
if (desc_count >=
(EFX_ADMA_TABLE_SZ / sizeof(struct efx_adma_desc))) {
dev_err(&host->pdev->dev, "Too many ADMA descriptors\n");
return -EINVAL;
}
efx_emmc_adma_set_desc(&desc[desc_count], align_addr, align_len,
EFX_ADMA_DESC_VALID | EFX_ADMA_DESC_TRAN);
offset += align_len;
len -= align_len;
desc_count++;
}
}
if (desc_count > 0) {
efx_emmc_adma_mark_end(&desc[desc_count - 1]);
}
}
if (desc_count == 0) {
dev_err(&host->pdev->dev, "No ADMA descriptors created\n");
return -EINVAL;
}
dma_sync_single_for_device(&host->pdev->dev, host->adma_desc_dma,
host->adma_desc_sz, DMA_TO_DEVICE);
return 0;
}
void efx_emmc_adma_table_post(struct efx_emmc_host *host, struct mmc_data *data)
{
dma_sync_single_for_cpu(&host->pdev->dev, host->adma_desc_dma,
host->adma_desc_sz, DMA_FROM_DEVICE);
if (host->bounce_buffer && (data->flags & MMC_DATA_READ)) {
struct scatterlist *sg;
char *bounce_pos;
int i;
unsigned int remaining;
if (!host->bounce_active)
goto out;
remaining = host->bounce_used;
bounce_pos = host->bounce_buffer;
for_each_sg(data->sg, sg, data->sg_len, i) {
unsigned int len = min(sg->length, remaining);
if (!len)
break;
memcpy(sg_virt(sg), bounce_pos, len);
bounce_pos += len;
remaining -= len;
}
}
out:
host->bounce_active = false;
host->bounce_used = 0;
}
void efx_emmc_prepare_dma(struct efx_emmc_host *host, struct mmc_data *data)
{
int ret;
if (!host->use_dma || !data) {
return;
}
ret = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len,
(data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE);
if (ret == 0) {
dev_err(&host->pdev->dev, "Failed to map DMA scatter-gather list\n");
host->use_dma = false;
return;
}
data->sg_len = ret;
ret = efx_emmc_adma_table_pre(host, data);
if (ret) {
dev_err(&host->pdev->dev, "Failed to setup ADMA table: %d\n", ret);
dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
(data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE);
host->use_dma = false;
return;
}
efx_emmc_set_adma_addr(host, host->adma_desc_dma);
}
void efx_emmc_cleanup_dma(struct efx_emmc_host *host, struct mmc_data *data)
{
if (!host->use_dma || !data) {
return;
}
efx_emmc_adma_table_post(host, data);
dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
(data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE);
}

View File

@@ -0,0 +1,370 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Efinix eMMC Host Controller Platform Driver
*
* Copyright (C) 2025 Efinix, Inc.
* Author: Teoh Choon Zone <czteoh@efinixinc.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include "efx_emmc.h"
static const struct mmc_host_ops efx_emmc_ops = {
.request = efx_emmc_request,
.set_ios = efx_emmc_set_ios,
.get_cd = efx_emmc_get_cd,
.get_ro = efx_emmc_get_ro,
.card_busy = efx_emmc_card_busy_wrapper,
.execute_tuning = efx_emmc_execute_tuning,
};
void efx_emmc_hs400_retune_work(struct work_struct *work)
{
struct efx_emmc_host *host = container_of(work, struct efx_emmc_host,
hs400_retune_work.work);
dev_info(&host->pdev->dev, "HS400 delayed retune worker started\n");
if (host->hs400_retune_pending &&
host->mmc &&
host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
dev_info(&host->pdev->dev,
"HS400 conditions met (clock=%u Hz), executing tuning\n",
host->mmc->ios.clock);
efx_emmc_execute_tuning(host->mmc, MMC_SEND_TUNING_BLOCK_HS200);
} else {
dev_warn(&host->pdev->dev,
"HS400 conditions not met: pending=%s, timing=%u, clock=%u\n",
host->hs400_retune_pending ? "true" : "false",
host->mmc ? host->mmc->ios.timing : 0,
host->mmc ? host->mmc->ios.clock : 0);
}
dev_info(&host->pdev->dev, "HS400 delayed retune worker completed\n");
}
void efx_emmc_reset_hw(struct efx_emmc_host *host)
{
u32 reg;
reg = efx_emmc_sys_readl(host, EFX_SYS_RESET_REG);
reg |= EFX_SYS_RESET_EMMC_IP;
efx_emmc_sys_writel(host, reg, EFX_SYS_RESET_REG);
udelay(EFX_EMMC_RESET_PULSE_WIDTH);
reg &= ~EFX_SYS_RESET_EMMC_IP;
efx_emmc_sys_writel(host, reg, EFX_SYS_RESET_REG);
udelay(EFX_EMMC_RESET_PULSE_WIDTH);
reg |= EFX_SYS_RESET_EMMC_DEV;
efx_emmc_sys_writel(host, reg, EFX_SYS_RESET_REG);
udelay(EFX_EMMC_RESET_PULSE_WIDTH);
reg &= ~EFX_SYS_RESET_EMMC_DEV;
efx_emmc_sys_writel(host, reg, EFX_SYS_RESET_REG);
udelay(EFX_EMMC_POST_RESET_DELAY);
}
int efx_emmc_init_hw(struct efx_emmc_host *host)
{
u32 caps, reg;
efx_emmc_reset_hw(host);
caps = efx_emmc_readl(host, EFX_EMMC_HOST_CAPABILITIES);
host->base_clk = (caps & 0x3FF) * 1000000;
if (host->base_clk == 0) {
host->base_clk = EFX_EMMC_BASE_CLK_FREQ_MHZ * 1000000;
}
dev_info(&host->pdev->dev, "Base clock: %u Hz, Capabilities: 0x%08x\n",
host->base_clk, caps);
efx_emmc_writel(host, 0, EFX_EMMC_INT_SIGNAL_EN);
efx_emmc_writel(host, 0, EFX_EMMC_INT_STATUS_EN);
efx_emmc_writel(host, EFX_EMMC_INT_ALL_MASK, EFX_EMMC_INT_STATUS);
reg = efx_emmc_readl(host, EFX_EMMC_HOST_CONTROL);
reg &= ~EFX_EMMC_DATA_WIDTH_MASK;
reg |= (EFX_EMMC_DATA_WIDTH_1BIT << EFX_EMMC_DATA_WIDTH_SHIFT);
efx_emmc_writel(host, reg, EFX_EMMC_HOST_CONTROL);
efx_emmc_set_clock(host, EFX_EMMC_MIN_FREQ);
msleep(10);
efx_emmc_writel(host, EFX_EMMC_INT_ALL_MASK, EFX_EMMC_INT_STATUS_EN);
efx_emmc_writel(host, EFX_EMMC_INT_ALL_MASK, EFX_EMMC_INT_SIGNAL_EN);
dev_info(&host->pdev->dev, "Hardware initialized successfully\n");
return 0;
}
int efx_emmc_probe(struct platform_device *pdev)
{
struct mmc_host *mmc;
struct efx_emmc_host *host;
struct resource *res;
int ret;
u32 version, present_state;
u32 hs400_defaults[2];
mmc = mmc_alloc_host(sizeof(struct efx_emmc_host), &pdev->dev);
if (!mmc) {
return -ENOMEM;
}
host = mmc_priv(mmc);
host->mmc = mmc;
host->pdev = pdev;
spin_lock_init(&host->lock);
host->tuning_done = false;
host->tuning_in_progress = false;
host->hs200_sample_count = 0;
host->hs200_pll_shift = 0;
host->hs200_margin = 0;
host->hs400_sample_count = 0;
host->hs400_pll_shift = 0;
host->hs400_margin = 0;
host->prev_timing = MMC_TIMING_LEGACY;
host->hs400_retune_pending = false;
host->tuned_timing_modes = 0;
INIT_DELAYED_WORK(&host->hs400_retune_work, efx_emmc_hs400_retune_work);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
host->ioaddr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->ioaddr)) {
ret = PTR_ERR(host->ioaddr);
goto err_free_host;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
host->sys_ioaddr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->sys_ioaddr)) {
ret = PTR_ERR(host->sys_ioaddr);
goto err_free_host;
}
host->hs400_default_sample = 0;
host->hs400_default_pll = 6;
if (!of_property_read_u32_array(pdev->dev.of_node,
"hs400-default-timing",
hs400_defaults, 2)) {
host->hs400_default_sample = hs400_defaults[0];
host->hs400_default_pll = hs400_defaults[1];
dev_info(&pdev->dev, "HS400 default timing from DT: sample=%u pll=%u\n",
host->hs400_default_sample, host->hs400_default_pll);
}
host->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(host->clk)) {
ret = PTR_ERR(host->clk);
dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
goto err_free_host;
}
ret = clk_prepare_enable(host->clk);
if (ret) {
dev_err(&pdev->dev, "Failed to enable clock: %d\n", ret);
goto err_free_host;
}
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "Failed to set DMA mask\n");
goto err_clk_disable;
}
host->dma_64bit = false;
host->adma_desc_sz = EFX_ADMA_TABLE_SZ;
host->adma_desc = dma_alloc_coherent(&pdev->dev, host->adma_desc_sz,
&host->adma_desc_dma, GFP_KERNEL);
if (!host->adma_desc) {
dev_err(&pdev->dev, "Failed to allocate ADMA descriptor table\n");
ret = -ENOMEM;
goto err_clk_disable;
}
host->bounce_buffer_size = 512 * 1024;
host->bounce_buffer = dma_alloc_coherent(&pdev->dev,
host->bounce_buffer_size,
&host->bounce_dma, GFP_KERNEL);
if (!host->bounce_buffer) {
dev_warn(&pdev->dev,
"Failed to allocate bounce buffer, using software alignment\n");
host->bounce_buffer_size = 0;
}
host->bounce_used = 0;
host->bounce_active = false;
host->irq = platform_get_irq(pdev, 0);
if (host->irq < 0) {
ret = host->irq;
goto err_free_dma;
}
ret = devm_request_irq(&pdev->dev, host->irq, efx_emmc_irq,
IRQF_SHARED, mmc_hostname(mmc), host);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
goto err_free_dma;
}
ret = efx_emmc_init_hw(host);
if (ret) {
goto err_free_dma;
}
version = efx_emmc_readl(host, EFX_EMMC_VERSION);
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
dev_info(&pdev->dev, "Version: 0x%08x, Present state: 0x%08x\n",
version, present_state);
ret = mmc_of_parse(mmc);
if (ret) {
dev_err(&pdev->dev, "Failed to parse DT: %d\n", ret);
goto err_free_dma;
}
mmc->ops = &efx_emmc_ops;
if (!mmc->f_min)
mmc->f_min = EFX_EMMC_MIN_FREQ;
if (!mmc->f_max)
mmc->f_max = EFX_EMMC_MAX_FREQ;
if (!(mmc->caps & MMC_CAP_CMD23))
mmc->caps |= MMC_CAP_CMD23;
if (!mmc->ocr_avail)
mmc->ocr_avail = MMC_VDD_165_195 | MMC_VDD_27_28 | MMC_VDD_28_29 |
MMC_VDD_29_30 | MMC_VDD_30_31 | MMC_VDD_31_32 |
MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_34_35 |
MMC_VDD_35_36;
mmc->max_seg_size = 65536;
mmc->max_segs = 128;
mmc->max_req_size = mmc->max_seg_size * mmc->max_segs;
mmc->max_blk_size = EFX_EMMC_MAX_BLOCK_LENGTH;
mmc->max_blk_count = 65535;
platform_set_drvdata(pdev, mmc);
ret = mmc_add_host(mmc);
if (ret) {
dev_err(&pdev->dev, "Failed to add MMC host: %d\n", ret);
goto err_free_dma;
}
mmc_detect_change(mmc, msecs_to_jiffies(500));
dev_info(&pdev->dev, "Efinix eMMC Host Controller registered (DMA: %s)\n",
host->adma_desc ? "enabled" : "disabled");
dev_info(&pdev->dev, "MMC caps: 0x%08x, OCR: 0x%08x\n",
mmc->caps, mmc->ocr_avail);
dev_info(&pdev->dev, "Clock range: %u - %u Hz\n", mmc->f_min, mmc->f_max);
dev_info(&pdev->dev, "Max block size: %u, Max segments: %u\n",
mmc->max_blk_size, mmc->max_segs);
dev_info(&pdev->dev,
"ADMA desc table: %zu bytes, Bounce buffer: %u bytes\n",
host->adma_desc_sz, host->bounce_buffer_size);
return 0;
err_free_dma:
if (host->bounce_buffer) {
dma_free_coherent(&pdev->dev, host->bounce_buffer_size,
host->bounce_buffer, host->bounce_dma);
}
if (host->adma_desc) {
dma_free_coherent(&pdev->dev, host->adma_desc_sz,
host->adma_desc, host->adma_desc_dma);
}
err_clk_disable:
clk_disable_unprepare(host->clk);
err_free_host:
mmc_free_host(mmc);
return ret;
}
int efx_emmc_remove(struct platform_device *pdev)
{
struct mmc_host *mmc;
struct efx_emmc_host *host;
mmc = platform_get_drvdata(pdev);
host = mmc_priv(mmc);
mmc_remove_host(mmc);
cancel_delayed_work_sync(&host->hs400_retune_work);
efx_emmc_writel(host, 0, EFX_EMMC_INT_SIGNAL_EN);
efx_emmc_writel(host, 0, EFX_EMMC_INT_STATUS_EN);
efx_emmc_reset_hw(host);
if (host->bounce_buffer) {
dma_free_coherent(&pdev->dev, host->bounce_buffer_size,
host->bounce_buffer, host->bounce_dma);
}
if (host->adma_desc) {
dma_free_coherent(&pdev->dev, host->adma_desc_sz,
host->adma_desc, host->adma_desc_dma);
}
clk_disable_unprepare(host->clk);
mmc_free_host(mmc);
return 0;
}
static const struct of_device_id efx_emmc_of_match[] = {
{ .compatible = "efinix,emmc-host-controller", },
{ }
};
MODULE_DEVICE_TABLE(of, efx_emmc_of_match);
static struct platform_driver efx_emmc_driver = {
.probe = efx_emmc_probe,
.remove = efx_emmc_remove,
.driver = {
.name = "efx-emmc",
.of_match_table = efx_emmc_of_match,
},
};
module_platform_driver(efx_emmc_driver);
MODULE_DESCRIPTION("Efinix eMMC Host Controller Driver with DMA Support");
MODULE_AUTHOR("Teoh Choon Zone <czteoh@efinixinc.com>");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");

View File

@@ -0,0 +1,459 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Efinix eMMC Host Controller Tuning Support
*
* Copyright (C) 2025 Efinix, Inc.
* Author: Teoh Choon Zone <czteoh@efinixinc.com>
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/mmc/mmc.h>
#include "efx_emmc.h"
static const u32 tuning_block_pattern_8b_mode[] = {
0xff00ffff, 0x0000ffff, 0xccccffff, 0xcccc33cc,
0xcc3333cc, 0xffffcccc, 0xffffeeff, 0xffeeeeff,
0xffddffff, 0xddddffff, 0xbbffffff, 0xbbffffff,
0xffffffbb, 0xffffff77, 0x77ff7777, 0xffeeddbb,
0x00ffffff, 0x00ffffff, 0xccffff00, 0xcc33cccc,
0x3333cccc, 0xffcccccc, 0xffeeffff, 0xeeeeffff,
0xddffffff, 0xddffffff, 0xffffffdd, 0xffffffbb,
0xffffbbbb, 0xffff77ff, 0xff7777ff, 0xeeddbb77
};
static const u32 tuning_block_pattern_4b_mode[] = {
0x00ff0fff, 0xccc3ccff, 0xffcc3cc3, 0xeffefffe,
0xddffdfff, 0xfbfffbff, 0xff7fffbf, 0xefbdf777,
0xf0fff0ff, 0x3cccfc0f, 0xcfcc33cc, 0xeeffefff,
0xfdfffdff, 0xffbfffdf, 0xfff7ffbb, 0xde7b7ff7
};
void efx_emmc_set_timing_config(struct efx_emmc_host *host,
u32 sample_count, u32 pll_shift)
{
u32 config_value;
config_value = (sample_count << 16) | (pll_shift << 6);
efx_emmc_writel(host, config_value | 0x0, EFX_EMMC_BASE_REG1);
efx_emmc_writel(host, config_value | 0x1, EFX_EMMC_BASE_REG1);
efx_emmc_writel(host, config_value | 0x0, EFX_EMMC_BASE_REG1);
udelay(100);
}
int efx_emmc_execute_tuning_command(struct efx_emmc_host *host, int bus_width)
{
u32 block_size, command_config, word_count;
const u32 *reference_pattern;
u32 received_data;
int i, mismatches = 0;
unsigned long timeout;
u32 tuning_present_state;
if (bus_width == 8) {
block_size = EFX_EMMC_TUNING_BLOCK_SIZE_8BIT;
word_count = 32;
reference_pattern = tuning_block_pattern_8b_mode;
} else {
block_size = EFX_EMMC_TUNING_BLOCK_SIZE_4BIT;
word_count = 16;
reference_pattern = tuning_block_pattern_4b_mode;
}
efx_emmc_writel(host, (1 << 16) | block_size, EFX_EMMC_BLOCK_SIZE);
efx_emmc_writel(host, 0x0, EFX_EMMC_ARG1);
efx_emmc_writel(host, 0xFFFFFFFF, EFX_EMMC_INT_STATUS);
command_config = 0x153A0010;
efx_emmc_writel(host, command_config, EFX_EMMC_TRANSFER_MODE);
timeout = jiffies + msecs_to_jiffies(5);
do {
tuning_present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
if (tuning_present_state & EFX_EMMC_BUFFER_READ_EN) {
break;
}
if (time_after(jiffies, timeout)) {
dev_dbg(&host->pdev->dev, "Tuning command timeout after 5ms\n");
return 0;
}
cpu_relax();
} while (1);
for (i = 0; i < word_count; i++) {
received_data = efx_emmc_readl(host, EFX_EMMC_BUFFER_DATA_PORT);
if (received_data != reference_pattern[i]) {
mismatches++;
dev_dbg(&host->pdev->dev,
"Tuning data mismatch at word %d: got 0x%08x, expected 0x%08x\n",
i, received_data, reference_pattern[i]);
}
}
if (mismatches <= 2) {
return 1;
} else {
dev_dbg(&host->pdev->dev, "Too many mismatches: %d\n", mismatches);
return 0;
}
}
static int efx_emmc_find_longest_consecutive_ones(u8 *row, int length)
{
int max_len, current_len, i;
max_len = 0;
current_len = 0;
for (i = 0; i < length; i++) {
if (row[i] == 1) {
current_len++;
if (current_len > max_len) {
max_len = current_len;
}
} else {
current_len = 0;
}
}
return max_len;
}
static int efx_emmc_find_center_of_consecutive_ones(u8 *row, int length)
{
int max_len, current_len, max_start, current_start, center, i;
max_len = 0;
current_len = 0;
max_start = 0;
current_start = 0;
for (i = 0; i < length; i++) {
if (row[i] == 1) {
if (current_len == 0) {
current_start = i;
}
current_len++;
if (current_len > max_len) {
max_len = current_len;
max_start = current_start;
}
} else {
current_len = 0;
}
}
center = max_start + (max_len / 2);
return center;
}
int efx_emmc_find_optimal_timing(struct efx_emmc_host *host,
u8 result_map[][EFX_EMMC_MAX_PLL_SHIFT],
u32 max_sample_count)
{
int max_consecutive_length, row_length, optimal_sample_count,
optimal_pll_shift;
int *optimal_rows;
int optimal_row_count, center_row, i;
char optimal_row_str[32];
optimal_rows = kmalloc(max_sample_count * sizeof(int), GFP_KERNEL);
if (!optimal_rows) {
return -ENOMEM;
}
dev_dbg(&host->pdev->dev, "Analyzing timing results\n");
max_consecutive_length = 0;
for (i = 0; i < max_sample_count; i++) {
row_length = efx_emmc_find_longest_consecutive_ones(result_map[i],
EFX_EMMC_MAX_PLL_SHIFT);
dev_dbg(&host->pdev->dev, "Sample[%u]: consecutive_length=%d\n",
i, row_length);
if (row_length > max_consecutive_length) {
dev_dbg(&host->pdev->dev,
"New best: Sample[%u] length=%d\n",
i, row_length);
max_consecutive_length = row_length;
}
}
dev_dbg(&host->pdev->dev, "Best consecutive length: %d\n",
max_consecutive_length);
if (max_consecutive_length < EFX_EMMC_MIN_TIMING_MARGIN) {
dev_warn(&host->pdev->dev,
"Insufficient timing margin: %d (minimum %d)\n",
max_consecutive_length, EFX_EMMC_MIN_TIMING_MARGIN);
}
optimal_row_count = 0;
for (i = 0; i < max_sample_count; i++) {
if (efx_emmc_find_longest_consecutive_ones(result_map[i],
EFX_EMMC_MAX_PLL_SHIFT) ==
max_consecutive_length) {
optimal_rows[optimal_row_count++] = i;
}
}
if (optimal_row_count == 0) {
kfree(optimal_rows);
return -ENODEV;
}
center_row = optimal_row_count / 2;
optimal_sample_count = optimal_rows[center_row];
dev_dbg(&host->pdev->dev,
"Selected sample_count=%d from %d optimal rows\n",
optimal_sample_count, optimal_row_count);
optimal_row_str[0] = '\0';
for (i = 0; i < EFX_EMMC_MAX_PLL_SHIFT; i++) {
sprintf(optimal_row_str + strlen(optimal_row_str), "%d",
result_map[optimal_sample_count][i]);
}
dev_dbg(&host->pdev->dev,
"Analyzing row[%d]: [%s] for center calculation\n",
optimal_sample_count, optimal_row_str);
optimal_pll_shift =
efx_emmc_find_center_of_consecutive_ones(result_map[optimal_sample_count],
EFX_EMMC_MAX_PLL_SHIFT);
dev_dbg(&host->pdev->dev,
"Center PLL calculation result: pll_shift=%d\n",
optimal_pll_shift);
if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200) {
host->hs200_sample_count = optimal_sample_count;
host->hs200_pll_shift = optimal_pll_shift;
host->hs200_margin = max_consecutive_length;
} else if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400) {
host->hs400_sample_count = optimal_sample_count;
host->hs400_pll_shift = optimal_pll_shift;
host->hs400_margin = max_consecutive_length;
}
dev_dbg(&host->pdev->dev,
"Optimal timing found: sample_count=%u, pll_shift=%u, margin=%d\n",
optimal_sample_count, optimal_pll_shift, max_consecutive_length);
kfree(optimal_rows);
return 0;
}
int efx_emmc_execute_tuning(struct mmc_host *mmc, u32 opcode)
{
struct efx_emmc_host *host;
u32 max_sample_count, sample_count, pll_shift;
int bus_width, success, ret;
unsigned long flags, timeout;
u8 (*tuning_result_map)[EFX_EMMC_MAX_PLL_SHIFT];
host = mmc_priv(mmc);
host->tuning_in_progress = true;
dev_info(&host->pdev->dev, "execute_tuning called: timing=%u, clock=%u Hz\n",
mmc->ios.timing, mmc->ios.clock);
if (!host->hs400_retune_pending &&
test_bit(mmc->ios.timing, &host->tuned_timing_modes) &&
host->tuning_done &&
(mmc->ios.timing == MMC_TIMING_MMC_HS200 ||
mmc->ios.timing == MMC_TIMING_MMC_HS400)) {
u32 sample = (mmc->ios.timing == MMC_TIMING_MMC_HS200) ?
host->hs200_sample_count : host->hs400_sample_count;
u32 pll = (mmc->ios.timing == MMC_TIMING_MMC_HS200) ?
host->hs200_pll_shift : host->hs400_pll_shift;
efx_emmc_set_timing_config(host, sample, pll);
dev_info(&host->pdev->dev,
"Tuning skipped: already successfully tuned for timing=%u (sample=%u, pll=%u)\n",
mmc->ios.timing, sample, pll);
return 0;
}
if (host->hs400_retune_pending &&
mmc->ios.timing == MMC_TIMING_MMC_HS400) {
dev_info(&host->pdev->dev,
"HS400 forced retuning (clock=%u Hz)\n",
mmc->ios.clock);
host->hs400_retune_pending = false;
}
if (opcode != MMC_SEND_TUNING_BLOCK &&
opcode != MMC_SEND_TUNING_BLOCK_HS200) {
dev_err(&host->pdev->dev, "Unsupported tuning opcode: %u\n", opcode);
return -EINVAL;
}
if (mmc->ios.timing == MMC_TIMING_MMC_HS400) {
dev_info(&host->pdev->dev,
"Tuning in HS400 mode (DDR has different timing than HS200 SDR)\n");
}
bus_width = (mmc->ios.bus_width == MMC_BUS_WIDTH_8) ? 8 : 4;
max_sample_count = host->clk_div ? host->clk_div : 1;
dev_dbg(&host->pdev->dev,
"Starting tuning algorithm (bus_width=%d, max_sample=%u)\n",
bus_width, max_sample_count);
tuning_result_map = kmalloc(max_sample_count *
sizeof(u8[EFX_EMMC_MAX_PLL_SHIFT]),
GFP_KERNEL);
if (!tuning_result_map) {
dev_err(&host->pdev->dev, "Failed to allocate tuning result map\n");
return -ENOMEM;
}
memset(tuning_result_map, 0,
max_sample_count * sizeof(u8[EFX_EMMC_MAX_PLL_SHIFT]));
dev_dbg(&host->pdev->dev, "Using clk_div=%u for sample count\n",
max_sample_count);
timeout = jiffies + msecs_to_jiffies(5000);
dev_dbg(&host->pdev->dev, "Testing %d samples × %d PLL positions\n",
max_sample_count, EFX_EMMC_MAX_PLL_SHIFT);
for (sample_count = 0; sample_count < max_sample_count; sample_count++) {
int consecutive_passes = 0;
for (pll_shift = 0; pll_shift < EFX_EMMC_MAX_PLL_SHIFT; pll_shift++) {
efx_emmc_set_timing_config(host, sample_count, pll_shift);
success = efx_emmc_execute_tuning_command(host, bus_width);
if (!success) {
tuning_result_map[sample_count][pll_shift] = 0;
consecutive_passes = 0;
} else {
tuning_result_map[sample_count][pll_shift] = 1;
consecutive_passes++;
}
dev_dbg(&host->pdev->dev,
"Tuning [%u][%u]: %s (consecutive: %d)\n",
sample_count, pll_shift, success ? "PASS" : "FAIL",
consecutive_passes);
if (time_after(jiffies, timeout)) {
dev_warn(&host->pdev->dev, "Tuning timeout after 5 seconds\n");
goto find_optimal;
}
}
}
find_optimal:
if (!test_bit(mmc->ios.timing, &host->tuned_timing_modes)) {
dev_info(&host->pdev->dev, "Tuning result map:\n");
for (sample_count = 0; sample_count < max_sample_count;
sample_count++) {
char row_str[32] = "";
for (pll_shift = 0; pll_shift < EFX_EMMC_MAX_PLL_SHIFT;
pll_shift++) {
sprintf(row_str + strlen(row_str), "%d",
tuning_result_map[sample_count][pll_shift]);
}
dev_info(&host->pdev->dev, "Sample[%u]: [%s]\n",
sample_count, row_str);
}
}
ret = efx_emmc_find_optimal_timing(host, tuning_result_map,
max_sample_count);
if (ret == 0) {
u32 sample, pll, margin;
if (mmc->ios.timing == MMC_TIMING_MMC_HS200) {
sample = host->hs200_sample_count;
pll = host->hs200_pll_shift;
margin = host->hs200_margin;
} else if (mmc->ios.timing == MMC_TIMING_MMC_HS400) {
sample = host->hs400_sample_count;
pll = host->hs400_pll_shift;
margin = host->hs400_margin;
} else {
sample = 0;
pll = 4;
margin = 0;
}
efx_emmc_set_timing_config(host, sample, pll);
if (!test_bit(mmc->ios.timing, &host->tuned_timing_modes)) {
dev_info(&host->pdev->dev,
"Tuning completed: sample=%u, pll=%u, margin=%u\n",
sample, pll, margin);
} else {
dev_info(&host->pdev->dev,
"Tuning reconfirmed: sample=%u, pll=%u\n",
sample, pll);
}
host->tuning_done = true;
set_bit(mmc->ios.timing, &host->tuned_timing_modes);
} else {
dev_warn(&host->pdev->dev,
"Tuning failed: %d, using fallback configuration\n",
ret);
if (max_sample_count == 1) {
efx_emmc_set_timing_config(host, 0, 2);
if (mmc->ios.timing == MMC_TIMING_MMC_HS200) {
host->hs200_sample_count = 0;
host->hs200_pll_shift = 2;
} else if (mmc->ios.timing == MMC_TIMING_MMC_HS400) {
host->hs400_sample_count = 0;
host->hs400_pll_shift = 2;
}
} else {
efx_emmc_set_timing_config(host, 1, 1);
if (mmc->ios.timing == MMC_TIMING_MMC_HS200) {
host->hs200_sample_count = 1;
host->hs200_pll_shift = 1;
} else if (mmc->ios.timing == MMC_TIMING_MMC_HS400) {
host->hs400_sample_count = 1;
host->hs400_pll_shift = 1;
}
}
host->tuning_done = true;
if (mmc->ios.timing != MMC_TIMING_MMC_HS400) {
set_bit(mmc->ios.timing, &host->tuned_timing_modes);
} else {
dev_warn(&host->pdev->dev,
"HS400 tuning fallback - will retry on next access\n");
}
ret = 0;
}
spin_lock_irqsave(&host->lock, flags);
host->prev_timing = mmc->ios.timing;
spin_unlock_irqrestore(&host->lock, flags);
kfree(tuning_result_map);
host->tuning_in_progress = false;
if (ret == 0) {
dev_info(&host->pdev->dev, "Tuning completed successfully\n");
} else {
dev_err(&host->pdev->dev, "Tuning failed: %d\n", ret);
}
return ret;
}

386
drivers/mmc/host/efx_sdio.h Normal file
View File

@@ -0,0 +1,386 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Efinix SDIO Host Controller Driver Header with DMA Support
*
* Copyright (C) 2026 Efinix, Inc.
* Author: Khor Swee Aun <sakhor@efinixinc.com>
*/
#ifndef __EFX_SDIO_H__
#define __EFX_SDIO_H__
#include <linux/types.h>
#include <linux/mmc/host.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
/* Compatibility macros */
#ifndef min3
#define min3(x, y, z) min(min(x, y), z)
#endif
/* ADMA descriptor definitions */
#define EFX_ADMA_DESC_VALID BIT(0)
#define EFX_ADMA_DESC_END BIT(1)
#define EFX_ADMA_DESC_INT BIT(2)
#define EFX_ADMA_DESC_NOP (0 << 4)
#define EFX_ADMA_DESC_TRAN (2 << 4)
#define EFX_ADMA_DESC_LINK (3 << 4)
#define EFX_ADMA_MAX_LEN 65536
#define EFX_ADMA_DESC_ALIGN 8
#define EFX_ADMA_TABLE_SZ (512 * 8) /* Support up to 512 descriptors */
/* DMA boundary sizes */
#define EFX_DMA_BOUNDARY_4K 0
#define EFX_DMA_BOUNDARY_8K 1
#define EFX_DMA_BOUNDARY_16K 2
#define EFX_DMA_BOUNDARY_32K 3
#define EFX_DMA_BOUNDARY_64K 4
#define EFX_DMA_BOUNDARY_128K 5
#define EFX_DMA_BOUNDARY_256K 6
#define EFX_DMA_BOUNDARY_512K 7
/* SDIO IP Register Offsets - Efinix SDIO Controller */
#define EFX_SDIO_VERSION 0x000
#define EFX_SDIO_BASE_REG0 0x004
#define EFX_SDIO_BASE_STATUS_REG0 0x008
#define EFX_SDIO_BASE_REG1 0x00C
#define EFX_SDIO_ARG2 0x100
#define EFX_SDIO_BLOCK_SIZE 0x104
#define EFX_SDIO_ARG1 0x108
#define EFX_SDIO_TRANSFER_MODE 0x10C
#define EFX_SDIO_RESPONSE0 0x110
#define EFX_SDIO_RESPONSE1 0x114
#define EFX_SDIO_RESPONSE2 0x118
#define EFX_SDIO_RESPONSE3 0x11C
#define EFX_SDIO_BUFFER_DATA_PORT 0x120
#define EFX_SDIO_PRESENT_STATE 0x124
#define EFX_SDIO_HOST_CONTROL 0x128
#define EFX_SDIO_INT_STATUS 0x130
#define EFX_SDIO_INT_STATUS_EN 0x134
#define EFX_SDIO_INT_SIGNAL_EN 0x138
#define EFX_SDIO_HOST_CAPABILITIES 0x140
#define EFX_SDIO_HOST_ADJUSTMENT 0x144
#define EFX_SDIO_ADMA_SYS_ADDR_LOW 0x158
#define EFX_SDIO_ADMA_SYS_ADDR_HIGH 0x15C
/* System Register Offsets */
#define EFX_SYS_DATE_REG 0x000
#define EFX_SYS_TEST_REG 0x004
#define EFX_SYS_RESET_REG 0x008
/* Base Register 0 (0x004) */
#define EFX_SDIO_BASE_REG0_CLK_EN BIT(16)
#define EFX_SDIO_BASE_REG0_CLK_DIV_MASK 0xFFFF
/* Base Status Register 0 (0x008) */
#define EFX_SDIO_BASE_STATUS_DAT_BUSY BIT(1)
#define EFX_SDIO_BASE_STATUS_CMD_BUSY BIT(0)
/* Base Register 1 (0x00C) */
#define EFX_SDIO_BASE_REG1_SAMPLE_CNT_SHIFT 16
#define EFX_SDIO_BASE_REG1_SAMPLE_CNT_MASK (0xFFFF << 16)
#define EFX_SDIO_BASE_REG1_PHASE_SHIFT 6
#define EFX_SDIO_BASE_REG1_PHASE_MASK (0x7 << 6)
#define EFX_SDIO_BASE_REG1_PHASE_PULSE BIT(0)
/* Block Size Register (0x104) */
#define EFX_SDIO_BLOCK_COUNT_SHIFT 16
#define EFX_SDIO_BLOCK_COUNT_MASK (0xFFFF << 16)
#define EFX_SDIO_BLOCK_SIZE_MASK 0xFFF
#define EFX_SDIO_DMA_BOUNDARY_SHIFT 12
#define EFX_SDIO_DMA_BOUNDARY_MASK (0x7 << 12)
/* Transfer Mode Register (0x10C) */
#define EFX_SDIO_CMD_INDEX_SHIFT 24
#define EFX_SDIO_CMD_INDEX_MASK (0x3F << 24)
#define EFX_SDIO_DATA_PRESENT BIT(21)
#define EFX_SDIO_CMD_INDEX_CHECK_EN BIT(20)
#define EFX_SDIO_CMD_CRC_CHECK_EN BIT(19)
#define EFX_SDIO_RESP_TYPE_SHIFT 16
#define EFX_SDIO_RESP_TYPE_MASK (0x3 << 16)
#define EFX_SDIO_RESP_TYPE_NONE 0
#define EFX_SDIO_RESP_TYPE_136 1
#define EFX_SDIO_RESP_TYPE_48 2
#define EFX_SDIO_RESP_TYPE_48_BUSY 3
#define EFX_SDIO_MULTI_BLOCK_SEL BIT(5)
#define EFX_SDIO_DATA_XFER_DIR BIT(4)
#define EFX_SDIO_AUTO_CMD_EN_SHIFT 2
#define EFX_SDIO_AUTO_CMD_EN_MASK (0x3 << 2)
#define EFX_SDIO_BLOCK_COUNT_EN BIT(1)
#define EFX_SDIO_DMA_EN BIT(0)
/* Present State Register (0x124) */
#define EFX_SDIO_DAT_0_SIG_LVL BIT(20)
#define EFX_SDIO_BUFFER_READ_EN BIT(11)
#define EFX_SDIO_BUFFER_WRITE_EN BIT(10)
#define EFX_SDIO_READ_XFER_ACTIVE BIT(9)
#define EFX_SDIO_WRITE_XFER_ACTIVE BIT(8)
#define EFX_SDIO_DAT_LINE_ACTIVE BIT(2)
#define EFX_SDIO_CMD_INHIBIT_DAT BIT(1)
#define EFX_SDIO_CMD_INHIBIT_CMD BIT(0)
/* Host Control Register (0x128) */
#define EFX_SDIO_DATA_SAMPLING_MODE BIT(3)
#define EFX_SDIO_DATA_WIDTH_SHIFT 1
#define EFX_SDIO_DATA_WIDTH_MASK (0x3 << 1)
#define EFX_SDIO_DATA_WIDTH_1BIT 0
#define EFX_SDIO_DATA_WIDTH_4BIT 1
#define EFX_SDIO_DATA_WIDTH_8BIT 2
/* Interrupt Status Register bits */
#define EFX_SDIO_INT_ADMA_ERROR BIT(25)
#define EFX_SDIO_INT_DATA_TIMEOUT_ERR BIT(22)
#define EFX_SDIO_INT_DATA_CRC_ERR BIT(21)
#define EFX_SDIO_INT_DATA_END_BIT_ERR BIT(20)
#define EFX_SDIO_INT_CMD_INDEX_ERR BIT(19)
#define EFX_SDIO_INT_CMD_END_BIT_ERR BIT(18)
#define EFX_SDIO_INT_CMD_CRC_ERR BIT(17)
#define EFX_SDIO_INT_CMD_TIMEOUT_ERR BIT(16)
#define EFX_SDIO_INT_CARD BIT(8)
#define EFX_SDIO_INT_BUFFER_READ_RDY BIT(5)
#define EFX_SDIO_INT_BUFFER_WRITE_RDY BIT(4)
#define EFX_SDIO_INT_DMA_INTERRUPT BIT(3)
#define EFX_SDIO_INT_BLOCK_GAP_EVENT BIT(2)
#define EFX_SDIO_INT_XFER_COMPLETE BIT(1)
#define EFX_SDIO_INT_CMD_COMPLETE BIT(0)
#define EFX_SDIO_INT_ERROR_MASK (EFX_SDIO_INT_ADMA_ERROR | \
EFX_SDIO_INT_DATA_TIMEOUT_ERR | \
EFX_SDIO_INT_DATA_CRC_ERR | \
EFX_SDIO_INT_DATA_END_BIT_ERR | \
EFX_SDIO_INT_CMD_INDEX_ERR | \
EFX_SDIO_INT_CMD_END_BIT_ERR | \
EFX_SDIO_INT_CMD_CRC_ERR | \
EFX_SDIO_INT_CMD_TIMEOUT_ERR)
/* SDIO Ccard interrupt not included in ALL_MASK as it is handled separately
* by mmc core during SDIO IO driver initialization
*/
#define EFX_SDIO_INT_ALL_MASK (EFX_SDIO_INT_ERROR_MASK | \
EFX_SDIO_INT_BUFFER_READ_RDY | \
EFX_SDIO_INT_BUFFER_WRITE_RDY | \
EFX_SDIO_INT_DMA_INTERRUPT | \
EFX_SDIO_INT_BLOCK_GAP_EVENT | \
EFX_SDIO_INT_XFER_COMPLETE | \
EFX_SDIO_INT_CMD_COMPLETE)
/* System Reset Register bits */
#define EFX_SYS_RESET_SDIO_DEV BIT(3)
#define EFX_SYS_RESET_SDIO_IP BIT(2)
/* Host capabilities */
#define EFX_SDIO_BASE_CLK_FREQ_MHZ 200
#define EFX_SDIO_MAX_BLOCK_LENGTH 512
#define EFX_SDIO_TIMEOUT_CLK_FREQ 200000000
#define EFX_SDIO_IO_VOLTAGE_1_8V 0
#define EFX_SDIO_IO_VOLTAGE_3_3V 1
/* Driver constants */
#define EFX_SDIO_MIN_FREQ 400000 /* 400 KHz */
#define EFX_SDIO_MAX_FREQ 200000000 /* 200 MHz */
#define EFX_SDIO_PIO_TIMEOUT_MS 1000
/* Hardware specific constants from documentation */
#define EFX_SDIO_CLOCK_STABILIZE_DELAY 1000 /* 1ms + 74 clock cycles */
#define EFX_SDIO_RESET_PULSE_WIDTH 1 /* 1us minimum */
#define EFX_SDIO_POST_RESET_DELAY 200 /* 200us minimum */
#define EFX_SDIO_CMD_RETRY_COUNT 3 /* Command retry attempts */
/* Tuning algorithm constants */
#define EFX_SDIO_MAX_PLL_SHIFT 8 /* 8 phase positions (45°) */
#define EFX_SDIO_PLL_SETTLING_TIME 50 /* 50ms PLL settling time */
#define EFX_SDIO_TUNING_TIMEOUT_MS 50 /* CMD19 timeout */
#define EFX_SDIO_TUNING_POLL_INTERVAL 200 /* 200us polling interval */
#define EFX_SDIO_TUNING_BLOCK_SIZE_4BIT 64 /* 4-bit bus tuning block */
#define EFX_SDIO_TUNING_BLOCK_SIZE_8BIT 128 /* 8-bit bus tuning block */
#define EFX_SDIO_MIN_TIMING_MARGIN 1 /* Min consecutive valid */
/**
* struct efx_adma_desc - ADMA descriptor structure
* @attr: Descriptor attributes (valid, end, interrupt, type)
* @len: Data length for this descriptor
* @addr: 32-bit DMA address
*
* Hardware ADMA descriptor structure, must be 8-byte aligned
*/
struct efx_adma_desc {
u16 attr;
u16 len;
u32 addr;
} __packed __aligned(8);
/**
* struct efx_sdio_host - Efinix SDIO host controller instance
* @mmc: MMC host structure
* @ioaddr: Base address for SDIO registers
* @sys_ioaddr: Base address for system registers
* @clk: Controller clock
* @irq: Interrupt number
* @mrq: Current MMC request
* @cmd: Current MMC command
* @data: Current MMC data transfer
* @base_clk: Base clock frequency
* @current_clk: Current configured clock frequency
* @bytes_to_transfer: Remaining bytes for PIO transfer
* @blocks_done: Number of completed blocks
* @sg_offset: Current offset in scatter-gather list
* @adma_desc: ADMA descriptor table
* @adma_desc_dma: DMA address of descriptor table
* @adma_desc_sz: Size of descriptor table
* @bounce_buffer: Bounce buffer for unaligned transfers
* @bounce_dma: DMA address of bounce buffer
* @bounce_buffer_size: Size of bounce buffer
* @use_dma: Flag indicating DMA mode is active
* @dma_64bit: Flag indicating 64-bit DMA support
* @clk_div: Current clock divider value
* @optimal_sample_count: Optimal sample count from tuning
* @optimal_pll_shift: Optimal PLL shift from tuning
* @optimal_margin: Timing margin from tuning
* @tuning_done: Flag indicating tuning completion
* @tuning_in_progress: Flag indicating active tuning
* @prev_timing: Previous timing mode
* @hs400_retune_pending: Flag indicating HS400 retune needed
* @hs400_retune_work: Delayed work for HS400 retuning
* @tuned_timing_modes: Bitmap of successfully tuned timing modes
* @lock: Spinlock for protecting shared data
* @pdev: Platform device
*/
struct efx_sdio_host {
struct mmc_host *mmc;
void __iomem *ioaddr;
void __iomem *sys_ioaddr;
struct clk *clk;
int irq;
u32 io_voltage;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
u32 base_clk;
u32 current_clk;
/* Transfer state tracking */
unsigned int bytes_to_transfer;
unsigned int blocks_done;
unsigned int sg_offset;
/* DMA related fields */
struct efx_adma_desc *adma_desc;
dma_addr_t adma_desc_dma;
size_t adma_desc_sz;
void *bounce_buffer;
dma_addr_t bounce_dma;
unsigned int bounce_buffer_size;
bool use_bounce;
bool use_dma;
bool dma_64bit;
/* Tuning related fields */
u32 clk_div;
u32 optimal_sample_count;
u32 optimal_pll_shift;
u32 optimal_margin;
bool tuning_done;
bool tuning_in_progress;
unsigned int prev_timing;
bool hs400_retune_pending;
bool tuning_crc_error;
struct delayed_work hs400_retune_work;
/* Tuning state bitmap - tracks which timing modes have been tuned */
unsigned long tuned_timing_modes;
spinlock_t lock;
struct platform_device *pdev;
};
/* Debug macros */
#define efx_sdio_dbg_irq(host, fmt, ...) \
dev_dbg(&(host)->pdev->dev, fmt, ##__VA_ARGS__)
#define efx_sdio_dbg_pio(host, fmt, ...) \
dev_dbg(&(host)->pdev->dev, fmt, ##__VA_ARGS__)
#define efx_sdio_dbg_cmd(host, fmt, ...) \
dev_dbg(&(host)->pdev->dev, fmt, ##__VA_ARGS__)
/* Platform driver function prototypes */
int efx_sdio_probe(struct platform_device *pdev);
int efx_sdio_remove(struct platform_device *pdev);
int efx_sdio_init_hw(struct efx_sdio_host *host);
void efx_sdio_reset_hw(struct efx_sdio_host *host);
void efx_sdio_hs400_retune_work(struct work_struct *work);
/* Core MMC host operation prototypes */
void efx_sdio_request(struct mmc_host *mmc, struct mmc_request *mrq);
void efx_sdio_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
int efx_sdio_get_cd(struct mmc_host *mmc);
int efx_sdio_card_busy_wrapper(struct mmc_host *mmc);
int efx_sdio_get_ro(struct mmc_host *mmc);
irqreturn_t efx_sdio_irq(int irq, void *dev_id);
void efx_sdio_enable_sdio_irq(struct mmc_host *host, int enable);
void efx_sdio_ack_sdio_irq(struct mmc_host *host);
int efx_sdio_start_signal_voltage_switch(struct mmc_host *mmc, struct mmc_ios *ios);
/* Core helper function prototypes */
void efx_sdio_send_command(struct efx_sdio_host *host, struct mmc_command *cmd);
void efx_sdio_finish_request(struct efx_sdio_host *host,
struct mmc_request *mrq);
void efx_sdio_finish_command(struct efx_sdio_host *host);
void efx_sdio_finish_data(struct efx_sdio_host *host);
void efx_sdio_transfer_pio(struct efx_sdio_host *host);
void efx_sdio_set_clock(struct efx_sdio_host *host, unsigned int clock);
void efx_sdio_set_bus_width(struct efx_sdio_host *host, int width);
void efx_sdio_set_timing(struct efx_sdio_host *host, unsigned int timing);
bool efx_sdio_card_busy(struct efx_sdio_host *host);
/* DMA function prototypes */
int efx_sdio_adma_table_pre(struct efx_sdio_host *host,
struct mmc_data *data);
void efx_sdio_adma_table_post(struct efx_sdio_host *host,
struct mmc_data *data);
void efx_sdio_prepare_dma(struct efx_sdio_host *host, struct mmc_data *data);
void efx_sdio_cleanup_dma(struct efx_sdio_host *host, struct mmc_data *data);
void efx_sdio_set_adma_addr(struct efx_sdio_host *host, dma_addr_t addr);
/* Tuning function prototypes */
int efx_sdio_execute_tuning(struct mmc_host *mmc, u32 opcode);
int efx_sdio_execute_tuning_command(struct efx_sdio_host *host,
int bus_width);
int efx_sdio_execute_custom_tuning_command(struct efx_sdio_host *host,
int bus_width);
void efx_sdio_set_timing_config(struct efx_sdio_host *host,
u32 sample_count, u32 pll_shift);
int efx_sdio_find_optimal_timing(struct efx_sdio_host *host,
u8 result_map[][EFX_SDIO_MAX_PLL_SHIFT],
u32 max_sample_count);
/* Register access helpers */
static inline u32 efx_sdio_readl(struct efx_sdio_host *host, u32 reg)
{
return readl(host->ioaddr + reg);
}
static inline void efx_sdio_writel(struct efx_sdio_host *host, u32 val, u32 reg)
{
writel(val, host->ioaddr + reg);
}
static inline u32 efx_sdio_sys_readl(struct efx_sdio_host *host, u32 reg)
{
return readl(host->sys_ioaddr + reg);
}
static inline void efx_sdio_sys_writel(struct efx_sdio_host *host, u32 val,
u32 reg)
{
writel(val, host->sys_ioaddr + reg);
}
#endif /* __EFX_SDIO_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Efinix SDIO Host Controller DMA Support
*
* Copyright (C) 2026 Efinix, Inc.
* Author: Khor Swee Aun <sakhor@efinixinc.com>
*/
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include "efx_sdio.h"
void efx_sdio_set_adma_addr(struct efx_sdio_host *host, dma_addr_t addr)
{
efx_sdio_writel(host, (u32)addr, EFX_SDIO_ADMA_SYS_ADDR_LOW);
if (host->dma_64bit) {
efx_sdio_writel(host, (u32)((u64)addr >> 32),
EFX_SDIO_ADMA_SYS_ADDR_HIGH);
}
}
static void efx_sdio_adma_mark_end(struct efx_adma_desc *desc)
{
desc->attr |= EFX_ADMA_DESC_END;
}
static void efx_sdio_adma_set_desc(struct efx_adma_desc *desc, u32 addr,
u16 len, u16 attr)
{
desc->attr = attr;
desc->len = len;
desc->addr = addr;
}
int efx_sdio_adma_table_pre(struct efx_sdio_host *host, struct mmc_data *data)
{
struct efx_adma_desc *desc;
struct scatterlist *sg;
dma_addr_t addr, align_addr;
u32 len, offset, align_len;
int i, desc_count = 0;
/* Init use_bounce flag */
host->use_bounce = false;
/* Check if we need bounce buffer due to alignment requirements */
for_each_sg(data->sg, sg, data->sg_len, i) {
addr = sg_dma_address(sg);
len = sg_dma_len(sg);
/* Check 4-byte alignment len requirement for DMA */
if (len & 0x3) {
host->use_bounce = true;
break;
}
}
if (host->use_bounce) {
/* Use bounce buffer for unaligned transfers */
if (!host->bounce_buffer) {
dev_err(&host->pdev->dev, "Bounce buffer not available\n");
return -ENOMEM;
}
if (data->blksz * data->blocks > host->bounce_buffer_size) {
dev_err(&host->pdev->dev, "Transfer too large for bounce buffer\n");
return -EINVAL;
}
/* Copy data to bounce buffer for write operations */
if (data->flags & MMC_DATA_WRITE) {
struct scatterlist *sg;
char *bounce_pos;
int i;
bounce_pos = host->bounce_buffer;
for_each_sg(data->sg, sg, data->sg_len, i) {
/* Copy data to bounce buffer */
memcpy(bounce_pos, sg_virt(sg), sg->length);
bounce_pos += sg->length;
}
}
/* Setup single descriptor for bounce buffer */
desc = host->adma_desc;
efx_sdio_adma_set_desc(desc, host->bounce_dma,
data->blksz * data->blocks,
EFX_ADMA_DESC_VALID | EFX_ADMA_DESC_TRAN);
efx_sdio_adma_mark_end(desc);
desc_count = 1;
} else {
/*Not using bounce buffer */
/* Setup descriptors for scatter-gather list */
desc = host->adma_desc;
for_each_sg(data->sg, sg, data->sg_len, i) {
addr = sg_dma_address(sg);
len = sg_dma_len(sg);
offset = 0;
while (len > 0) {
align_addr = addr + offset;
align_len = min(len, (u32)EFX_ADMA_MAX_LEN);
if (desc_count >=
(EFX_ADMA_TABLE_SZ / sizeof(struct efx_adma_desc))) {
dev_err(&host->pdev->dev, "Too many ADMA descriptors\n");
return -EINVAL;
}
efx_sdio_adma_set_desc(&desc[desc_count], align_addr, align_len,
EFX_ADMA_DESC_VALID | EFX_ADMA_DESC_TRAN);
offset += align_len;
len -= align_len;
desc_count++;
}
}
if (desc_count > 0) {
efx_sdio_adma_mark_end(&desc[desc_count - 1]);
}
}
if (desc_count == 0) {
dev_err(&host->pdev->dev, "No ADMA descriptors created\n");
return -EINVAL;
}
return 0;
}
void efx_sdio_adma_table_post(struct efx_sdio_host *host, struct mmc_data *data)
{
/* If bounce buffer is used, copy data from bounce buffer for read operations */
if (data->flags & MMC_DATA_READ) {
if (host->use_bounce) {
struct scatterlist *sg;
char *bounce_pos;
int i;
bounce_pos = host->bounce_buffer;
dma_sync_single_for_cpu(&host->pdev->dev, host->bounce_dma,
data->blksz * data->blocks, DMA_FROM_DEVICE);
for_each_sg(data->sg, sg, data->sg_len, i) {
/* Copy data from bounce buffer */
memcpy(sg_virt(sg), bounce_pos, sg->length);
bounce_pos += sg->length;
}
} else {
// Sync scatter-gather list for CPU from device
dma_sync_sg_for_cpu(&host->pdev->dev, data->sg, data->sg_len,
DMA_FROM_DEVICE);
}
}
}
void efx_sdio_prepare_dma(struct efx_sdio_host *host, struct mmc_data *data)
{
int ret;
if (!host->use_dma || !data) {
return;
}
// Can not do DMA on a block size is not 4-byte aligned
if ( (data->blksz ) & 3) {
dev_warn(&host->pdev->dev,
"Data block size %u not 4-byte aligned, falling back to PIO\n",
data->blksz);
host->use_dma = false;
return;
}
/* Map scatter-gather list for DMA */
ret = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len,
(data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE);
if (ret == 0) {
dev_err(&host->pdev->dev, "Failed to map DMA scatter-gather list\n");
host->use_dma = false;
return;
}
data->sg_len = ret;
/* Setup ADMA descriptor table */
ret = efx_sdio_adma_table_pre(host, data);
if (ret) {
dev_err(&host->pdev->dev, "Failed to setup ADMA table: %d\n", ret);
dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
(data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE);
host->use_dma = false;
return;
}
/* Set ADMA system address */
efx_sdio_set_adma_addr(host, host->adma_desc_dma);
}
void efx_sdio_cleanup_dma(struct efx_sdio_host *host, struct mmc_data *data)
{
if (!host->use_dma || !data) {
return;
}
/* Post-process ADMA table */
efx_sdio_adma_table_post(host, data);
/* Unmap scatter-gather list */
dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
(data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE);
}

View File

@@ -0,0 +1,426 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Efinix SDIO Host Controller Platform Driver
*
* Copyright (C) 2026 Efinix, Inc.
* Author: Khor Swee Aun <sakhor@efinixinc.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include "efx_sdio.h"
static const struct mmc_host_ops efx_sdio_ops = {
.request = efx_sdio_request,
.set_ios = efx_sdio_set_ios,
.get_cd = efx_sdio_get_cd,
.get_ro = efx_sdio_get_ro,
.card_busy = efx_sdio_card_busy_wrapper,
.enable_sdio_irq = efx_sdio_enable_sdio_irq,
.ack_sdio_irq = efx_sdio_ack_sdio_irq,
.execute_tuning = efx_sdio_execute_tuning,
.start_signal_voltage_switch = efx_sdio_start_signal_voltage_switch,
};
/**
* efx_sdio_reset_hw - Reset SDIO IP and device
* @host: SDIO host controller instance
*
* Performs hardware reset sequence according to SDIO specification:
* 1. Reset IP core (minimum 1us pulse)
* 2. Reset SDIO device (minimum 1us pulse)
* 3. Wait for device initialization (200us minimum)
*/
void efx_sdio_reset_hw(struct efx_sdio_host *host)
{
u32 reg;
/* Reset SDIO IP - minimum 1us pulse width per documentation */
reg = efx_sdio_sys_readl(host, EFX_SYS_RESET_REG);
reg |= EFX_SYS_RESET_SDIO_IP;
efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG);
udelay(EFX_SDIO_RESET_PULSE_WIDTH);
/* Release IP reset */
reg &= ~EFX_SYS_RESET_SDIO_IP;
efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG);
udelay(EFX_SDIO_RESET_PULSE_WIDTH);
/* Reset SDIO device - minimum 1us pulse width (tRSTW) */
reg |= EFX_SYS_RESET_SDIO_DEV;
efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG);
udelay(EFX_SDIO_RESET_PULSE_WIDTH);
/* Release device reset */
reg &= ~EFX_SYS_RESET_SDIO_DEV;
efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG);
/* Wait 200us (tRSCA) or 74 clock cycles per documentation */
udelay(EFX_SDIO_POST_RESET_DELAY);
}
/**
* efx_sdio_init_hw - Initialize SDIO hardware
* @host: SDIO host controller instance
*
* Initializes the SDIO controller hardware including:
* - Hardware reset
* - Capability reading and base clock setup
* - Interrupt configuration
* - Initial bus width and clock settings
*
* Return: 0 on success, negative error code on failure
*/
int efx_sdio_init_hw(struct efx_sdio_host *host)
{
u32 caps, reg;
/* Reset hardware */
efx_sdio_reset_hw(host);
/* Read capabilities */
caps = efx_sdio_readl(host, EFX_SDIO_HOST_CAPABILITIES);
host->base_clk = (caps & 0x3FF) * 1000000; /* Convert MHz to Hz */
if (host->base_clk == 0) {
host->base_clk = EFX_SDIO_BASE_CLK_FREQ_MHZ * 1000000;
}
host->io_voltage = (caps >> 12) & 0xF;
dev_info(&host->pdev->dev, "Base clock: %u Hz, IO Voltage: %sV, Capabilities: 0x%08x\n",
host->base_clk, host->io_voltage == EFX_SDIO_IO_VOLTAGE_1_8V ? "1.8" : "3.0", caps);
/* Disable all interrupts initially */
efx_sdio_writel(host, 0, EFX_SDIO_INT_SIGNAL_EN);
efx_sdio_writel(host, 0, EFX_SDIO_INT_STATUS_EN);
/* Clear any pending interrupts */
efx_sdio_writel(host, EFX_SDIO_INT_ALL_MASK, EFX_SDIO_INT_STATUS);
/* Set initial bus width to 1-bit */
reg = efx_sdio_readl(host, EFX_SDIO_HOST_CONTROL);
reg &= ~EFX_SDIO_DATA_WIDTH_MASK;
reg |= (EFX_SDIO_DATA_WIDTH_1BIT << EFX_SDIO_DATA_WIDTH_SHIFT);
/*Bit 4 for IB or OOB interrupt
* Mask bit 4 for IB interrupt
*reg |= 0x10; Set bit 4 for OOB interrupt
*/
reg &= ~0x10;
efx_sdio_writel(host, reg, EFX_SDIO_HOST_CONTROL);
/* Set initial clock to identification frequency */
efx_sdio_set_clock(host, EFX_SDIO_MIN_FREQ);
/* Wait for hardware to stabilize */
msleep(10);
/* Enable interrupts */
efx_sdio_writel(host, EFX_SDIO_INT_ALL_MASK, EFX_SDIO_INT_STATUS_EN);
efx_sdio_writel(host, EFX_SDIO_INT_ALL_MASK, EFX_SDIO_INT_SIGNAL_EN);
dev_info(&host->pdev->dev, "Hardware initialized successfully\n");
return 0;
}
int efx_sdio_probe(struct platform_device *pdev)
{
struct mmc_host *mmc;
struct efx_sdio_host *host;
struct resource *res;
int ret;
u32 version, present_state;
mmc = mmc_alloc_host(sizeof(struct efx_sdio_host), &pdev->dev);
if (!mmc) {
return -ENOMEM;
}
host = mmc_priv(mmc);
host->mmc = mmc;
host->pdev = pdev;
spin_lock_init(&host->lock);
/* Initialize tuning-related fields */
host->tuning_done = false;
host->tuning_in_progress = false;
host->optimal_sample_count = 0;
host->optimal_pll_shift = 0;
host->optimal_margin = 0;
host->prev_timing = MMC_TIMING_LEGACY;
host->hs400_retune_pending = false;
host->tuned_timing_modes = 0; /* Clear all bits - no modes tuned yet */
/* Get memory resources */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
host->ioaddr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->ioaddr)) {
ret = PTR_ERR(host->ioaddr);
goto err_free_host;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
host->sys_ioaddr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->sys_ioaddr)) {
ret = PTR_ERR(host->sys_ioaddr);
goto err_free_host;
}
/* Get clock */
host->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(host->clk)) {
ret = PTR_ERR(host->clk);
dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
goto err_free_host;
}
ret = clk_prepare_enable(host->clk);
if (ret) {
dev_err(&pdev->dev, "Failed to enable clock: %d\n", ret);
goto err_free_host;
}
/* Set up DMA mask */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "Failed to set DMA mask\n");
goto err_clk_disable;
}
host->dma_64bit = false;
} else {
host->dma_64bit = true;
}
/* Allocate ADMA descriptor table */
host->adma_desc_sz = EFX_ADMA_TABLE_SZ;
host->adma_desc = dma_alloc_coherent(&pdev->dev, host->adma_desc_sz,
&host->adma_desc_dma, GFP_KERNEL);
if (!host->adma_desc) {
dev_err(&pdev->dev, "Failed to allocate ADMA descriptor table\n");
ret = -ENOMEM;
goto err_clk_disable;
}
/* Allocate bounce buffer for unaligned transfers */
host->bounce_buffer_size = 512 * 1024;
host->bounce_buffer = dma_alloc_coherent(&pdev->dev,
host->bounce_buffer_size,
&host->bounce_dma, GFP_KERNEL);
if (!host->bounce_buffer) {
dev_warn(&pdev->dev,
"Failed to allocate bounce buffer, using software alignment\n");
host->bounce_buffer_size = 0;
}
/* Get IRQ */
host->irq = platform_get_irq(pdev, 0);
if (host->irq < 0) {
ret = host->irq;
goto err_free_dma;
}
ret = devm_request_irq(&pdev->dev, host->irq, efx_sdio_irq,
IRQF_SHARED, mmc_hostname(mmc), host);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
goto err_free_dma;
}
/* Initialize hardware */
ret = efx_sdio_init_hw(host);
if (ret) {
goto err_free_dma;
}
/* Read version register to verify hardware is accessible */
version = efx_sdio_readl(host, EFX_SDIO_VERSION);
present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
dev_info(&pdev->dev, "Version: 0x%08x, Present state: 0x%08x\n",
version, present_state);
/* Set up MMC host */
mmc->ops = &efx_sdio_ops;
mmc->f_min = EFX_SDIO_MIN_FREQ;
mmc->f_max = EFX_SDIO_MAX_FREQ;
/* SDIO-specific capabilities */
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
/* Not support SD and eMMC
SDIO IRQ NOTHREAD
*/
mmc->caps2 = MMC_CAP2_NO_SD | MMC_CAP2_NO_MMC | MMC_CAP2_SDIO_IRQ_NOTHREAD;
/* Clear all UHS capability bits first */
mmc->caps &= ~MMC_CAP_UHS;
/* Read UHS mode flags directly from DTS */
struct device_node *np = pdev->dev.of_node;
bool uhs_set = false;
if (np) {
if (of_property_read_bool(np, "sd-uhs-sdr25")) {
mmc->caps |= MMC_CAP_UHS_SDR25;
uhs_set = true;
}
if (of_property_read_bool(np, "sd-uhs-ddr50")) {
mmc->caps |= MMC_CAP_UHS_DDR50;
uhs_set = true;
}
if (of_property_read_bool(np, "sd-uhs-sdr104")) {
mmc->caps |= MMC_CAP_UHS_SDR104;
uhs_set = true;
}
}
/* Default if no UHS mode specified in DTS */
if (!uhs_set) {
mmc->caps |= MMC_CAP_UHS_SDR25;
dev_info(&pdev->dev,
"No UHS mode in DTS, defaulting to SDR25\n");
} else {
dev_info(&pdev->dev,
"UHS modes: %s%s%s\n",
(mmc->caps & MMC_CAP_UHS_SDR25) ? "SDR25 " : "",
(mmc->caps & MMC_CAP_UHS_DDR50) ? "DDR50 " : "",
(mmc->caps & MMC_CAP_UHS_SDR104) ? "SDR104 " : "");
}
/* Voltage support: 1.7-1.95V and 2.7-3.6V */
mmc->ocr_avail = MMC_VDD_165_195 | MMC_VDD_27_28 | MMC_VDD_28_29 |
MMC_VDD_29_30 | MMC_VDD_30_31 | MMC_VDD_31_32 |
MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_34_35 |
MMC_VDD_35_36;
// Maximum segment size each scatter-gather descriptor can handle
mmc->max_seg_size = 65536;
// Maximum number of scatter-gather segments per request
mmc->max_segs = 128;
// Maximum request size in bytes for all scatter-gather descriptors
mmc->max_req_size = mmc->max_seg_size * mmc->max_segs;
// Maximum block size
mmc->max_blk_size = EFX_SDIO_MAX_BLOCK_LENGTH;
// Maximum number of blocks per request
mmc->max_blk_count = 65535;
platform_set_drvdata(pdev, mmc);
ret = mmc_add_host(mmc);
if (ret) {
dev_err(&pdev->dev, "Failed to add SDIO host: %d\n", ret);
goto err_free_dma;
}
/* Force card detection after a delay */
mmc_detect_change(mmc, msecs_to_jiffies(500));
dev_info(&pdev->dev, "Efinix SDIO Host Controller registered (DMA: %s)\n",
host->adma_desc ? "enabled" : "disabled");
dev_info(&pdev->dev, "SDIO caps: 0x%08x, OCR: 0x%08x\n",
mmc->caps, mmc->ocr_avail);
dev_info(&pdev->dev, "Clock range: %u - %u Hz\n", mmc->f_min, mmc->f_max);
dev_info(&pdev->dev, "Max block size: %u, Max segments: %u\n",
mmc->max_blk_size, mmc->max_segs);
dev_info(&pdev->dev,
"ADMA desc table: %zu bytes, Bounce buffer: %u bytes\n",
host->adma_desc_sz, host->bounce_buffer_size);
return 0;
err_free_dma:
if (host->bounce_buffer) {
dma_free_coherent(&pdev->dev, host->bounce_buffer_size,
host->bounce_buffer, host->bounce_dma);
}
if (host->adma_desc) {
dma_free_coherent(&pdev->dev, host->adma_desc_sz,
host->adma_desc, host->adma_desc_dma);
}
err_clk_disable:
clk_disable_unprepare(host->clk);
err_free_host:
mmc_free_host(mmc);
return ret;
}
int efx_sdio_remove(struct platform_device *pdev)
{
struct mmc_host *mmc;
struct efx_sdio_host *host;
mmc = platform_get_drvdata(pdev);
host = mmc_priv(mmc);
mmc_remove_host(mmc);
/* Cancel any pending delayed work */
//SA cancel_delayed_work_sync(&host->hs400_retune_work);
/* Disable interrupts */
efx_sdio_writel(host, 0, EFX_SDIO_INT_SIGNAL_EN);
efx_sdio_writel(host, 0, EFX_SDIO_INT_STATUS_EN);
/* Reset hardware */
efx_sdio_reset_hw(host);
/* Free DMA resources */
if (host->bounce_buffer) {
dma_free_coherent(&pdev->dev, host->bounce_buffer_size,
host->bounce_buffer, host->bounce_dma);
}
if (host->adma_desc) {
dma_free_coherent(&pdev->dev, host->adma_desc_sz,
host->adma_desc, host->adma_desc_dma);
}
clk_disable_unprepare(host->clk);
mmc_free_host(mmc);
dev_info(&pdev->dev, "Efinix SDIO Host Controller removed\n");
return 0;
}
static const struct of_device_id efx_sdio_of_match[] = {
{ .compatible = "efinix,sdio-host-controller", },
{ }
};
MODULE_DEVICE_TABLE(of, efx_sdio_of_match);
static struct platform_driver efx_sdio_driver = {
.probe = efx_sdio_probe,
.remove = efx_sdio_remove,
.driver = {
.name = "efx-sdio",
.of_match_table = efx_sdio_of_match,
},
};
module_platform_driver(efx_sdio_driver);
MODULE_DESCRIPTION("Efinix SDIO Host Controller Driver with DMA Support");
MODULE_AUTHOR("Khor Swee Aun <sakhor@efinixinc.com>");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");

View File

@@ -0,0 +1,681 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Efinix SDIO Host Controller Tuning Support
*
* Copyright (C) 2026 Efinix, Inc.
* Author: Khor Swee Aun <sakhor@efinixinc.com>
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/mmc/mmc.h>
#include "efx_sdio.h"
/* Standard eMMC/SDIO tuning block patterns from bare metal driver */
static const u32 tuning_block_pattern_8b_mode[] = {
0xff00ffff, 0x0000ffff, 0xccccffff, 0xcccc33cc,
0xcc3333cc, 0xffffcccc, 0xffffeeff, 0xffeeeeff,
0xffddffff, 0xddddffff, 0xbbffffff, 0xbbffffff,
0xffffffbb, 0xffffff77, 0x77ff7777, 0xffeeddbb,
0x00ffffff, 0x00ffffff, 0xccffff00, 0xcc33cccc,
0x3333cccc, 0xffcccccc, 0xffeeffff, 0xeeeeffff,
0xddffffff, 0xddffffff, 0xffffffdd, 0xffffffbb,
0xffffbbbb, 0xffff77ff, 0xff7777ff, 0xeeddbb77
};
static const u32 tuning_block_pattern_4b_mode[] = {
0x00ff0fff, 0xccc3ccff, 0xffcc3cc3, 0xeffefffe,
0xddffdfff, 0xfbfffbff, 0xff7fffbf, 0xefbdf777,
0xf0fff0ff, 0x3cccfc0f, 0xcfcc33cc, 0xeeffefff,
0xfdfffdff, 0xffbfffdf, 0xfff7ffbb, 0xde7b7ff7
};
void efx_sdio_set_timing_config(struct efx_sdio_host *host,
u32 sample_count, u32 pll_shift)
{
u32 config_value;
/* Build timing configuration: sample_count[31:16] | pll_shift[8:6] */
config_value = (sample_count << 16) | (pll_shift << 6);
/* Apply timing configuration with hardware trigger sequence:
* 1. Write config with trigger bit clear (bit 0 = 0)
* 2. Write config with trigger bit set (bit 0 = 1) to latch settings
*/
efx_sdio_writel(host, config_value | 0x0, EFX_SDIO_BASE_REG1);
efx_sdio_writel(host, config_value | 0x1, EFX_SDIO_BASE_REG1);
efx_sdio_writel(host, config_value | 0x0, EFX_SDIO_BASE_REG1);
/* Wait for PLL settling */
udelay(100); /* 100us is sufficient for PLL settling */
}
/* Execute custom tuning command for SDR12, SDR25 and DDR50 modes
* Reminder : This function is called with host lock held
*/
int efx_sdio_execute_custom_tuning_command(struct efx_sdio_host *host, int bus_width)
{
u32 block_size, command_config, word_count;
//const u32 *reference_pattern;
u32 received_data;
int i, mismatches = 0;
unsigned long timeout;
u32 tuning_present_state;
unsigned long flags;
u32 reg_addr = 0x0;
// Reset CRC error flag at start of tuning command
host->tuning_crc_error = false;
/* Determine block size and reference pattern based on bus width */
block_size = 16;
word_count = block_size/4;
//reference_pattern = NULL;
/* Configure arguments for CMD53
[31] R/W flag = 0
[30:28] Function number = 0
[27] Block mode = 0
[26] OP code (fixed/increment) = 1
[25:9] Register address = reg_addr
[8:0] Byte count / block count = 16 (byte)
*/
u32 arg = 0;
arg |= ( 1 << 26 | reg_addr << 9 | 16 );
efx_sdio_writel(host, arg, EFX_SDIO_ARG1);
/* Byte mode
For byte mode, block size is set to the byte count and block count is set to 1
Might not neeeded as this is a byte mode transfer
*/
efx_sdio_writel(host, (1 << 16) | block_size, EFX_SDIO_BLOCK_SIZE);
/* Configure command based on single or multi-block transfer */
u32 val = 0;
u32 cmd_index = 53; // READ_SINGLE_BLOCK command
u32 data_available = 1; // Data transfer expected
u32 cmd_index_check_en = 1; // Enable command index check
u32 cmd_crc_en = 1; // Enable command CRC check
u32 resp_type = 2; // R1 response type (48-bit)
u32 data_direction = 1; // Read from card
u32 auto_cmd_en = 0; // No auto command
u32 multi_block_en = 0; // Single block transfer
u32 block_counter_en = 0; // Block counter disabled
u32 dma_mode = 0; // DMA mode disabled
/* Build command register value from configuration bits */
val = (cmd_index << 24) | (data_available << 21) | (cmd_index_check_en << 20) |
(cmd_crc_en << 19) | (resp_type << 16) | (multi_block_en << 5) |
(data_direction << 4) | (auto_cmd_en << 2) | (block_counter_en << 1) | (dma_mode << 0);
efx_sdio_writel(host, val, EFX_SDIO_TRANSFER_MODE);
/* Wait for buffer ready with timeout */
timeout = jiffies + msecs_to_jiffies(5);
do {
tuning_present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
if (tuning_present_state & EFX_SDIO_BUFFER_READ_EN) {
break;
}
if (time_after(jiffies, timeout)) {
dev_dbg(&host->pdev->dev, "Custom tuning command timeout after 5ms\n");
return 0; /* Failure */
}
cpu_relax();
} while (1);
/* Read custom tuning data */
for (i = 0; i < word_count; i++) {
received_data = efx_sdio_readl(host, EFX_SDIO_BUFFER_DATA_PORT);
/* Print received data for debugging */
dev_dbg(&host->pdev->dev, "Received custom tuning data word %d: 0x%08x\n", i, received_data);
}
/* Delay 1ms */
udelay(1000);
if (host->tuning_crc_error) {
dev_dbg(&host->pdev->dev,
"Custom tuning command CRC error detected\n");
host->tuning_crc_error = false; /* Reset CRC error flag after handling */
return 0; /* Failure due to CRC error */
}
return 1; /* Success */
}
int efx_sdio_execute_tuning_command(struct efx_sdio_host *host, int bus_width)
{
u32 block_size, command_config, word_count;
const u32 *reference_pattern;
u32 received_data;
int i, mismatches = 0;
unsigned long timeout;
u32 tuning_present_state;
unsigned long flags;
/* Determine block size and reference pattern based on bus width */
if (bus_width == 8) {
block_size = EFX_SDIO_TUNING_BLOCK_SIZE_8BIT;
word_count = 32;
reference_pattern = tuning_block_pattern_8b_mode;
} else {
block_size = EFX_SDIO_TUNING_BLOCK_SIZE_4BIT;
word_count = 16;
reference_pattern = tuning_block_pattern_4b_mode;
}
/* Configure command parameters for CMD19 */
efx_sdio_writel(host, (1 << 16) | block_size, EFX_SDIO_BLOCK_SIZE);
efx_sdio_writel(host, 0x0, EFX_SDIO_ARG1);
/* Issue CMD19 tuning command with specific configuration */
/* CMD19 (index=19), data present, CRC check, 48-bit response */
//
command_config = 0x133A0010;
spin_lock_irqsave(&host->lock, flags);
host->tuning_crc_error = false; // Reset CRC error flag at start of tuning command
spin_unlock_irqrestore(&host->lock, flags);
efx_sdio_writel(host, command_config, EFX_SDIO_TRANSFER_MODE);
/* Wait for buffer ready with timeout */
timeout = jiffies + msecs_to_jiffies(5);
do {
tuning_present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
if (tuning_present_state & EFX_SDIO_BUFFER_READ_EN) {
break;
}
if (time_after(jiffies, timeout)) {
dev_dbg(&host->pdev->dev, "Tuning command timeout after 5ms\n");
return 0; /* Failure */
}
cpu_relax();
} while (1);
/* Read tuning data and compare against expected pattern */
for (i = 0; i < word_count; i++) {
received_data = efx_sdio_readl(host, EFX_SDIO_BUFFER_DATA_PORT);
/* Compare against standard pattern */
if (received_data != reference_pattern[i]) {
mismatches++;
dev_dbg(&host->pdev->dev,
"Tuning data mismatch at word %d: got 0x%08x, expected 0x%08x\n",
i, received_data, reference_pattern[i]);
}
}
udelay(1000);
if (host->tuning_crc_error) {
dev_dbg(&host->pdev->dev,
"Tuning command CRC error detected\n");
host->tuning_crc_error = false; /* Reset CRC error flag after handling */
return 0; /* Failure due to CRC error */
}
/* Allow up to 2 mismatches due to electrical noise during tuning */
// SDIO: change to 0 mismatches for stricter tuning
if (mismatches == 0) {
return 1; /* Success */
} else {
dev_dbg(&host->pdev->dev, "Too many mismatches: %d\n", mismatches);
return 0; /* Failure */
}
}
static int efx_sdio_find_longest_consecutive_ones(u8 *row, int length)
{
int max_len, current_len, i;
max_len = 0;
current_len = 0;
for (i = 0; i < length; i++) {
if (row[i] == 1) {
current_len++;
if (current_len > max_len) {
max_len = current_len;
}
} else {
current_len = 0;
}
}
return max_len;
}
static int efx_sdio_find_center_of_consecutive_ones(u8 *row, int length)
{
int max_len, current_len, max_start, current_start, center, i;
max_len = 0;
current_len = 0;
max_start = 0;
current_start = 0;
for (i = 0; i < length; i++) {
if (row[i] == 1) {
if (current_len == 0) {
current_start = i;
}
current_len++;
if (current_len > max_len) {
max_len = current_len;
max_start = current_start;
}
} else {
current_len = 0;
}
}
/* Return center of longest consecutive sequence */
center = max_start + (max_len / 2);
return center;
}
int efx_sdio_find_optimal_timing(struct efx_sdio_host *host,
u8 result_map[][EFX_SDIO_MAX_PLL_SHIFT],
u32 max_sample_count)
{
int max_consecutive_length, row_length, optimal_sample_count,
optimal_pll_shift;
int *optimal_rows = NULL;
int optimal_row_count, center_row, i;
char optimal_row_str[32];
// Max sample count should be less than 8 With system clock is 200Mhz and target SDIO clock is 25Mhz.
// Shall perform basic tuning if the max sample count more than 8.
if (max_sample_count > 8) {
optimal_rows = kmalloc(max_sample_count * sizeof(int), GFP_KERNEL);
if (!optimal_rows) {
return -ENOMEM;
}
/* Find rows with longest consecutive 1's */
dev_dbg(&host->pdev->dev, "Analyzing timing results\n");
max_consecutive_length = 0;
for (i = 0; i < max_sample_count; i++) {
row_length = efx_sdio_find_longest_consecutive_ones(result_map[i],
EFX_SDIO_MAX_PLL_SHIFT);
dev_dbg(&host->pdev->dev, "Sample[%u]: consecutive_length=%d\n",
i, row_length);
if (row_length > max_consecutive_length) {
dev_dbg(&host->pdev->dev,
"New best: Sample[%u] length=%d\n",
i, row_length);
max_consecutive_length = row_length;
}
}
dev_dbg(&host->pdev->dev, "Best consecutive length: %d\n",
max_consecutive_length);
if (max_consecutive_length < EFX_SDIO_MIN_TIMING_MARGIN) {
dev_warn(&host->pdev->dev,
"Insufficient timing margin: %d (minimum %d)\n",
max_consecutive_length, EFX_SDIO_MIN_TIMING_MARGIN);
}
/* Collect all rows with maximum consecutive length */
optimal_row_count = 0;
for (i = 0; i < max_sample_count; i++) {
if (efx_sdio_find_longest_consecutive_ones(result_map[i],
EFX_SDIO_MAX_PLL_SHIFT) ==
max_consecutive_length) {
optimal_rows[optimal_row_count++] = i;
}
}
if (optimal_row_count == 0) {
kfree(optimal_rows);
return -ENODEV; /* No valid configurations found */
}
/* Find center row */
center_row = optimal_row_count / 2;
optimal_sample_count = optimal_rows[center_row];
/* Find center column within optimal row */
dev_dbg(&host->pdev->dev,
"Selected sample_count=%d from %d optimal rows\n",
optimal_sample_count, optimal_row_count);
/* Debug: Show the row being analyzed for center calculation */
optimal_row_str[0] = '\0';
for (i = 0; i < EFX_SDIO_MAX_PLL_SHIFT; i++) {
sprintf(optimal_row_str + strlen(optimal_row_str), "%d",
result_map[optimal_sample_count][i]);
}
dev_dbg(&host->pdev->dev,
"Analyzing row[%d]: [%s] for center calculation\n",
optimal_sample_count, optimal_row_str);
optimal_pll_shift =
efx_sdio_find_center_of_consecutive_ones(result_map[optimal_sample_count],
EFX_SDIO_MAX_PLL_SHIFT);
dev_dbg(&host->pdev->dev,
"Center PLL calculation result: pll_shift=%d\n",
optimal_pll_shift);
// Check before free
if (optimal_rows) {
kfree(optimal_rows);
}
} else {
u8 *flat_map;
int flat_length;
// Append the same result_map to make it look like 1D for better center calculation
flat_length = max_sample_count * 2 * EFX_SDIO_MAX_PLL_SHIFT;
flat_map = kmalloc(flat_length, GFP_KERNEL);
if (!flat_map) {
return -ENOMEM;
}
int idx = 0;
for (i = 0; i < max_sample_count; i++) {
memcpy(&flat_map[idx], result_map[i], EFX_SDIO_MAX_PLL_SHIFT);
idx += EFX_SDIO_MAX_PLL_SHIFT;
}
for (i = 0; i < max_sample_count; i++) {
memcpy(&flat_map[idx], result_map[i], EFX_SDIO_MAX_PLL_SHIFT);
idx += EFX_SDIO_MAX_PLL_SHIFT;
}
//Debug: Print the flat map for analysis
dev_dbg(&host->pdev->dev, "Flat map:\n");
for (i = 0; i < flat_length; i++) {
dev_dbg(&host->pdev->dev, "flat_map[%d] = %d\n", i, flat_map[i]);
}
max_consecutive_length = efx_sdio_find_longest_consecutive_ones(flat_map, flat_length);
dev_dbg(&host->pdev->dev, "Best consecutive length in flat map: %d\n", max_consecutive_length);
if (max_consecutive_length < EFX_SDIO_MIN_TIMING_MARGIN) {
dev_warn(&host->pdev->dev,
"Insufficient timing margin in flat map: %d (minimum %d)\n",
max_consecutive_length, EFX_SDIO_MIN_TIMING_MARGIN);
}
// DDR and SDR modes using different selection strategy for better tuning results
// DDR mode: select center of longest consecutive 1's for better stability as DDR is more sensitive to timing
// SDR mode: select first occurrence of longest consecutive 1's for better performance as SDR is less sensitive to timing and can benefit from more aggressive settings
if (host->mmc->ios.timing == MMC_TIMING_UHS_DDR50) {
int center_idx = efx_sdio_find_center_of_consecutive_ones(flat_map, flat_length);
optimal_sample_count = (center_idx%(EFX_SDIO_MAX_PLL_SHIFT * max_sample_count)) / EFX_SDIO_MAX_PLL_SHIFT;
optimal_pll_shift = center_idx % EFX_SDIO_MAX_PLL_SHIFT;
dev_info(&host->pdev->dev,
"DDR mode: Selected center of longest sequence at index %d (sample_count=%d, pll_shift=%d)\n",
center_idx, optimal_sample_count, optimal_pll_shift);
} else {
// For SDR modes, find the first occurrence of the longest consecutive 1's
int i, j;
optimal_sample_count = 0;
optimal_pll_shift = 0;
bool found = false;
for (i = 0; i < flat_length; i++) {
if (flat_map[i] == 1) {
int current_length = 1;
for (j = i + 1; j < flat_length && flat_map[j] == 1; j++) {
current_length++;
}
if (current_length == max_consecutive_length) {
optimal_sample_count = (i%(EFX_SDIO_MAX_PLL_SHIFT * max_sample_count))/EFX_SDIO_MAX_PLL_SHIFT;
optimal_pll_shift = i % EFX_SDIO_MAX_PLL_SHIFT;
found = true;
break;
}
}
}
dev_info(&host->pdev->dev,
"SDR mode: Selected first occurrence of longest sequence at index %d (sample_count=%d, pll_shift=%d)\n",
optimal_sample_count * EFX_SDIO_MAX_PLL_SHIFT + optimal_pll_shift,
optimal_sample_count, optimal_pll_shift);
}
kfree(flat_map);
}
/* Store optimal configuration */
host->optimal_sample_count = optimal_sample_count;
host->optimal_pll_shift = optimal_pll_shift;
host->optimal_margin = max_consecutive_length;
dev_dbg(&host->pdev->dev,
"Optimal timing found: sample_count=%u, pll_shift=%u, margin=%d\n",
optimal_sample_count, optimal_pll_shift, max_consecutive_length);
return 0;
}
/* Execute tuning procedure for given opcode
* Reminder: For custom tuning, this function is called with host lock released
*/
int efx_sdio_execute_tuning(struct mmc_host *mmc, u32 opcode)
{
struct efx_sdio_host *host;
u32 max_sample_count, sample_count, pll_shift;
int bus_width, success, ret;
unsigned long flags, timeout;
u8 (*tuning_result_map)[EFX_SDIO_MAX_PLL_SHIFT];
u32 tuning_loop = 10;
host = mmc_priv(mmc);
/* Set tuning in progress flag to suppress error logging */
host->tuning_in_progress = true;
dev_dbg(&host->pdev->dev, "Starting tuning: timing=%u, clock=%u Hz\n",
mmc->ios.timing, mmc->ios.clock);
/* Skip tuning if already tuned for current mode */
if (host->prev_timing == mmc->ios.timing && host->tuning_done) {
dev_dbg(&host->pdev->dev,
"Tuning skipped: already tuned for timing=%u (sample=%u, pll=%u)\n",
mmc->ios.timing, host->optimal_sample_count,
host->optimal_pll_shift);
return 0;
}
/* Validate SDIO tuning opcode
* MMC_SEND_TUNING_BLOCK or UINT_MAX for custom tuning
*/
if (opcode != MMC_SEND_TUNING_BLOCK && opcode != UINT_MAX) {
dev_err(&host->pdev->dev, "Unsupported tuning opcode: %u\n", opcode);
return -EINVAL;
}
/* Valid bus widths is 4-bit */
if (mmc->ios.bus_width != MMC_BUS_WIDTH_4) {
dev_warn(&host->pdev->dev,
"UHS-I Tuning only supported for 4-bit bus width, current width=%d\n",
mmc->ios.bus_width);
return -EINVAL;
}
bus_width = 4;
/* max_sample_count should match clk_div - sample count can't exceed
* clock divider
*/
/* Use actual clock divider */
max_sample_count = host->clk_div ? host->clk_div : 1;
dev_dbg(&host->pdev->dev,
"Starting tuning algorithm (bus_width=%d, max_sample=%u)\n",
bus_width, max_sample_count);
/* Dynamically allocate tuning result map */
tuning_result_map = kmalloc(max_sample_count *
sizeof(u8[EFX_SDIO_MAX_PLL_SHIFT]),
GFP_KERNEL);
if (!tuning_result_map) {
dev_err(&host->pdev->dev, "Failed to allocate tuning result map\n");
return -ENOMEM;
}
// Init entire map to 1
memset(tuning_result_map, 1,
max_sample_count * sizeof(u8[EFX_SDIO_MAX_PLL_SHIFT]));
dev_dbg(&host->pdev->dev, "Using clk_div=%u for sample count\n",
max_sample_count);
/* Set tuning timeout to 10 seconds */
timeout = jiffies + msecs_to_jiffies(10000);
/* Tuning loop */
for (tuning_loop = 0; tuning_loop < 10; tuning_loop++) {
dev_dbg(&host->pdev->dev, "Tuning loop %d:\n", tuning_loop + 1);
/* Phase 2: Timing configuration search */
dev_dbg(&host->pdev->dev, "Testing %d samples × %d PLL positions\n",
max_sample_count, EFX_SDIO_MAX_PLL_SHIFT);
for (sample_count = 0; sample_count < max_sample_count; sample_count++) {
int consecutive_passes = 0;
for (pll_shift = 0; pll_shift < EFX_SDIO_MAX_PLL_SHIFT; pll_shift++) {
dev_dbg(&host->pdev->dev,
"Tuning loop %d: Testing sample_count=%u, pll_shift=%u\n",
tuning_loop + 1, sample_count, pll_shift);
/* Apply timing configuration */
efx_sdio_set_timing_config(host, sample_count, pll_shift);
/* Execute validation test */
if (opcode == MMC_SEND_TUNING_BLOCK) {
success = efx_sdio_execute_tuning_command(host, bus_width);
} else {
/* Custom tuning - implement specific test if needed */
success = efx_sdio_execute_custom_tuning_command(host, bus_width);
}
// for non-DDR modes, additional checking needed to ensure result is successful
// success only 1 if bit 8 and bit 9 of EFX_SDIO_HOST_ADJUSTMENT register are both 1
if (success && mmc->ios.timing != MMC_TIMING_UHS_DDR50) {
u32 host_adjustment = efx_sdio_readl(host, EFX_SDIO_HOST_ADJUSTMENT);
if ((host_adjustment & 0x300) != 0x300) {
success = 0; // Mark as failure if either bit 8 or bit 9 is not set
dev_dbg(&host->pdev->dev,
"Additional check failed: host_adjustment=0x%08x\n",
host_adjustment);
}
}
/* Update result map */
if (!success) {
dev_dbg(&host->pdev->dev,
"Tuning [%u][%u]: FAIL\n",
sample_count, pll_shift);
// Only update the unsuccessful result
tuning_result_map[sample_count][pll_shift] = 0;
consecutive_passes = 0;
} else {
consecutive_passes++;
}
dev_dbg(&host->pdev->dev,
"Tuning [%u][%u]: %s (consecutive: %d)\n",
sample_count, pll_shift, success ? "PASS" : "FAIL",
consecutive_passes);
/* Check timeout */
if (time_after(jiffies, timeout)) {
dev_warn(&host->pdev->dev, "Tuning timeout after 10 seconds\n");
goto find_optimal;
}
}
}
}
find_optimal:
/* Print timing map results - only for new timing modes */
if (!test_bit(mmc->ios.timing, &host->tuned_timing_modes)) {
dev_info(&host->pdev->dev, "Tuning result map:\n");
for (sample_count = 0; sample_count < max_sample_count;
sample_count++) {
char row_str[32] = "";
for (pll_shift = 0; pll_shift < EFX_SDIO_MAX_PLL_SHIFT;
pll_shift++) {
sprintf(row_str + strlen(row_str), "%d",
tuning_result_map[sample_count][pll_shift]);
}
dev_info(&host->pdev->dev, "Sample[%u]: [%s]\n",
sample_count, row_str);
}
}
/* Phase 4: Find optimal timing configuration using dynamic map */
ret = efx_sdio_find_optimal_timing(host, tuning_result_map,
max_sample_count);
if (ret == 0) {
/* Use optimal timing found by tuning algorithm */
efx_sdio_set_timing_config(host, host->optimal_sample_count,
host->optimal_pll_shift);
/* Show detailed results only for new timing modes */
if (!test_bit(mmc->ios.timing, &host->tuned_timing_modes)) {
dev_info(&host->pdev->dev,
"Tuning completed: sample=%u, pll=%u, margin=%u\n",
host->optimal_sample_count, host->optimal_pll_shift,
host->optimal_margin);
} else {
dev_info(&host->pdev->dev,
"Tuning reconfirmed: sample=%u, pll=%u\n",
host->optimal_sample_count, host->optimal_pll_shift);
}
host->tuning_done = true;
/* Mark this timing mode as successfully tuned */
set_bit(mmc->ios.timing, &host->tuned_timing_modes);
} else {
/* Fallback to safe timing configuration */
dev_warn(&host->pdev->dev,
"Tuning failed: %d, using fallback configuration\n",
ret);
if (max_sample_count == 1) {
efx_sdio_set_timing_config(host, 0, 2); /* Safe for 200MHz */
host->optimal_sample_count = 0;
host->optimal_pll_shift = 2;
} else {
/* Conservative default timing */
efx_sdio_set_timing_config(host, 1, 1);
host->optimal_sample_count = 1;
host->optimal_pll_shift = 1;
}
host->tuning_done = true; /* Mark as done to prevent retry loops */
/* Mark this timing mode as successfully tuned (fallback) */
set_bit(mmc->ios.timing, &host->tuned_timing_modes);
ret = 0; /* Return success to allow operation to continue */
}
/* Update previous timing for reference */
spin_lock_irqsave(&host->lock, flags);
host->prev_timing = mmc->ios.timing;
spin_unlock_irqrestore(&host->lock, flags);
/* Free dynamically allocated tuning result map */
kfree(tuning_result_map);
/* Clear tuning in progress flag */
host->tuning_in_progress = false;
if (ret == 0) {
dev_dbg(&host->pdev->dev, "Tuning completed successfully\n");
} else {
dev_err(&host->pdev->dev, "Tuning failed: %d\n", ret);
}
return ret;
}

View File

@@ -59,6 +59,8 @@ static const struct flash_info issi_parts[] = {
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
SPI_NOR_4B_OPCODES)
.fixups = &is25lp256_fixups },
{ "is25wp512m", INFO(0x9d701a, 0, 64 * 1024, 512,
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
/* PMC */
{ "pm25lv512", INFO(0, 0, 32 * 1024, 2, SECT_4K_PMC) },

View File

@@ -240,6 +240,10 @@ static int rtl8211f_config_init(struct phy_device *phydev)
val_rxdly ? "enabled" : "disabled");
}
/* RTL8211F has an issue when operating in Gigabit slave mode */
phy_set_bits(phydev, MII_CTRL1000,
CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER);
return 0;
}

View File

@@ -7,8 +7,9 @@
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <linux/delay.h>
#define DRV_NAME "spinal-lib,spi-1.0"
#define DRV_NAME "spinal-lib,spi-1.1"
#define SPI_CMD_WRITE (1 << 8)
#define SPI_CMD_READ (1 << 9)
@@ -52,6 +53,7 @@ struct spi_spinal_lib {
/* data buffers */
const u8 *tx;
u8 *rx;
bool dummy_cycle;
};
static inline struct spi_spinal_lib *spi_spinal_lib_to_hw(struct spi_device *sdev)
@@ -79,9 +81,9 @@ static void spi_spinal_lib_cmd_wait(struct spi_spinal_lib *hw){
while(spi_spinal_lib_cmd_availability(hw) == 0) cpu_relax();
}
//static void spi_spinal_lib_rsp_wait(struct spi_spinal_lib *hw){
// while(spi_spinal_lib_rsp_occupancy(hw) == 0) cpu_relax();
//}
static void spi_spinal_lib_rsp_wait(struct spi_spinal_lib *hw){
while(spi_spinal_lib_rsp_occupancy(hw) == 0) cpu_relax();
}
static u32 spi_spinal_lib_rsp_pull(struct spi_spinal_lib *hw){
u32 rsp;
@@ -106,11 +108,75 @@ static void spi_spinal_lib_speed(struct spi_spinal_lib *hw, u32 speed_hz){
writel(clk_divider, hw->base + SPI_SPINAL_LIB_SS_HOLD);
}
static int spi_spinal_lib_tx(struct spi_spinal_lib *hw, u32 token)
{
u32 burst, cmd;
const u8 *ptr, *end;
burst = min(hw->len - hw->txCount, token);
ptr = hw->tx + hw->txCount;
end = ptr + burst;
cmd = (hw->tx ? SPI_CMD_WRITE : 0) | SPI_CMD_READ;
if(hw->tx) {
while(ptr != end) {
spi_spinal_lib_cmd_wait(hw);
writel(cmd | *ptr++, hw->base + SPI_SPINAL_LIB_DATA);
}
} else {
while(ptr != end){
ptr++;
spi_spinal_lib_cmd_wait(hw);
writel(cmd, hw->base + SPI_SPINAL_LIB_DATA);
}
}
hw->txCount += burst;
return burst;
}
static int spi_spinal_lib_rx(struct spi_spinal_lib *hw)
{
u32 burst;
u8 *ptr, *end;
u8 *rptr;
burst = spi_spinal_lib_rsp_occupancy(hw);
ptr = hw->rx + hw->count;
end = ptr + burst;
rptr = hw->rx;
if(hw->rx) {
/* this only works for spi flash */
if (hw->dummy_cycle) {
while(ptr != end) {
spi_spinal_lib_rsp_wait(hw);
*rptr = spi_spinal_lib_rsp_pull(hw);
ptr++;
}
} else {
/* this only works for spi sd card */
while (ptr != end) {
spi_spinal_lib_rsp_wait(hw);
*ptr++ = spi_spinal_lib_rsp(hw);
}
}
} else {
while(ptr != end) {
ptr++;
spi_spinal_lib_rsp(hw);
}
}
hw->count += burst;
udelay(10);
return burst;
}
static int spi_spinal_lib_txrx(struct spi_master *master, struct spi_device *spi, struct spi_transfer *t)
{
struct spi_spinal_lib *hw = spi_master_get_devdata(master);
spi_spinal_lib_speed(hw, t->speed_hz);
hw->tx = t->tx_buf;
@@ -120,59 +186,34 @@ static int spi_spinal_lib_txrx(struct spi_master *master, struct spi_device *spi
hw->bytes_per_word = DIV_ROUND_UP(t->bits_per_word, 8);
hw->len = t->len / hw->bytes_per_word;
if (hw->irq >= 0) {
dev_info(&master->dev, "Interrupt not implemented\n");
/* enable receive interrupt */
// hw->imr |= spi_spinal_lib_CONTROL_IRRDY_MSK;
// writel(hw->imr, hw->base + spi_spinal_lib_CONTROL);
/* send the first byte */
// spi_spinal_lib_tx_word(hw);
} else {
if(hw->cmdFifoDepth > 1 && hw->rspFifoDepth > 1){
u32 cmd = (hw->tx ? SPI_CMD_WRITE : 0) | SPI_CMD_READ;
u32 token = min(hw->cmdFifoDepth, hw->rspFifoDepth);
while (hw->count < hw->len) {
{ //rsp
u32 burst;
u8 *ptr, *end;
burst = spi_spinal_lib_rsp_occupancy(hw);
ptr = hw->rx + hw->count;
end = ptr + burst;
if(hw->rx) {while(ptr != end) {*ptr++ = spi_spinal_lib_rsp(hw);}}
else {while(ptr != end) { ptr++; spi_spinal_lib_rsp(hw);}}
hw->count += burst;
token += burst;
}
{ //cmd
u32 burst;
const u8 *ptr, *end;
burst = min(hw->len - hw->txCount, token);
ptr = hw->tx + hw->txCount;
end = ptr + burst;
if(hw->tx) {while(ptr != end) {writel(cmd | *ptr++, hw->base + SPI_SPINAL_LIB_DATA);}}
else {while(ptr != end) {ptr++; writel(cmd, hw->base + SPI_SPINAL_LIB_DATA);}}
hw->txCount += burst;
token -= burst;
}
}
} else {
u32 cmd = (hw->tx ? SPI_CMD_WRITE : 0) | SPI_CMD_READ;
while (hw->count < hw->len) {
u32 data = hw->tx ? hw->tx[hw->count] : 0;
writel(cmd | data, hw->base + SPI_SPINAL_LIB_DATA);
data = spi_spinal_lib_rsp_pull(hw);
if (hw->rx) hw->rx[hw->count] = data;
hw->count++;
}
}
spi_finalize_current_transfer(master);
if (hw->dummy_cycle) {
/*
* +1 when reading data using spi. this will add 1 more dummy
* write for read operation of spi flash.
*/
if (hw->rx)
hw->len += 1;
}
if(hw->cmdFifoDepth > 1 && hw->rspFifoDepth > 1){
u32 token = min(hw->cmdFifoDepth, hw->rspFifoDepth);
while (hw->count < hw->len) {
token += spi_spinal_lib_rx(hw);
token -= spi_spinal_lib_tx(hw, token);
}
} else {
u32 cmd = (hw->tx ? SPI_CMD_WRITE : 0) | SPI_CMD_READ;
while (hw->count < hw->len) {
u32 data = hw->tx ? hw->tx[hw->count] : 0;
writel(cmd | data, hw->base + SPI_SPINAL_LIB_DATA);
data = spi_spinal_lib_rsp_pull(hw);
if (hw->rx) hw->rx[hw->count] = data;
hw->count++;
}
}
spi_finalize_current_transfer(master);
return t->len;
}
@@ -219,7 +260,6 @@ static int spi_spinal_lib_setup(struct spi_device *spi)
config |= SPI_MODE_CPHA;
writel(config, hw->base + SPI_SPINAL_LIB_CONFIG);
// printk("Setup %d %d\n", hw->ssActiveHigh, config);
return 0;
}
@@ -269,6 +309,12 @@ static int spi_spinal_lib_probe(struct platform_device *pdev)
goto exit;
}
hw->dummy_cycle = false;
if (of_property_read_bool(pdev->dev.of_node, "dummy-cycle")) {
hw->dummy_cycle = true;
dev_info(&pdev->dev, "SPI controller configure with dummy clock cycle\n");
}
/* find and map our resources */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hw->base = devm_ioremap_resource(&pdev->dev, res);

View File

@@ -2206,6 +2206,15 @@ config FB_SIMPLE
Configuration re: surface address, size, and format must be provided
through device tree, or plain old platform data.
config FB_EFX
bool "Efinix framebuffer support"
depends on FB
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
help
Efinix framebuffer support
config FB_SSD1307
tristate "Solomon SSD1307 framebuffer support"
depends on FB && I2C

View File

@@ -118,6 +118,7 @@ obj-$(CONFIG_FB_MB862XX) += mb862xx/
obj-$(CONFIG_FB_HYPERV) += hyperv_fb.o
obj-$(CONFIG_FB_OPENCORES) += ocfb.o
obj-$(CONFIG_FB_SM712) += sm712fb.o
obj-$(CONFIG_FB_EFX) += fb_efx.o
# Platform or fallback drivers go here
obj-$(CONFIG_FB_UVESA) += uvesafb.o

View File

@@ -0,0 +1,555 @@
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/io.h>
#include <linux/of_platform.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#define DRIVER_NAME "efx-framebuffer"
#define EFX_MAX_FB_SIZE (4 * 1024 * 1024)
struct efxfb_format {
const char *name;
u32 bits_per_pixel;
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
};
struct efxfb_params {
u32 width;
u32 height;
u32 stride;
struct efxfb_format *format;
};
#define PSEUDO_PALETTE_SIZE 16
struct efxfb_par {
u32 palette[PSEUDO_PALETTE_SIZE];
void __iomem *display_buff_io;
void __iomem *base;
char *display_buf;
u32 fb_size;
int fb_transfer_mode;
/* DMA */
struct dma_chan *chan;
dma_addr_t dma_display_buff_io;
struct completion transfer_ok;
size_t nents;
struct scatterlist *sg;
struct page **pages;
size_t segments;
struct efxfb_params *params;
struct platform_device *pdev;
};
static const struct fb_fix_screeninfo efxfb_fix = {
.id = "efxfb",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_TRUECOLOR,
.accel = FB_ACCEL_NONE,
};
static const struct fb_var_screeninfo efxfb_var = {
.height = -1,
.width = -1,
.activate = FB_ACTIVATE_NOW,
.vmode = FB_VMODE_NONINTERLACED,
};
static int efxfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
u_int transp, struct fb_info *info)
{
u32 *pal = info->pseudo_palette;
u32 cr = red >> (16 - info->var.red.length);
u32 cg = green >> (16 - info->var.green.length);
u32 cb = blue >> (16 - info->var.blue.length);
u32 value;
if (regno >= PSEUDO_PALETTE_SIZE)
return -EINVAL;
value = (cr << info->var.red.offset) |
(cg << info->var.green.offset) |
(cb << info->var.blue.offset);
if (info->var.transp.length > 0) {
u32 mask = (1 << info->var.transp.length) - 1;
mask <<= info->var.transp.offset;
value |= mask;
}
pal[regno] = value;
return 0;
}
static int efxfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long size = vma->vm_end - vma->vm_start;
struct efxfb_par *par = info->par;
unsigned long page_start = (unsigned long)par->display_buf + offset;
unsigned long page_count = PAGE_ALIGN(size) >> PAGE_SHIFT;
unsigned long start = vma->vm_start;
unsigned long pfn;
int i;
for (i = 0; i < page_count; i++) {
pfn = vmalloc_to_pfn((void *)(page_start + (i << PAGE_SHIFT)));
if(remap_pfn_range(vma, start + (i << PAGE_SHIFT), pfn, PAGE_SIZE, vma->vm_page_prot)) {
return -EAGAIN;
}
}
return 0;
}
static void efxfb_destroy(struct fb_info *info)
{
if (info->screen_base)
iounmap(info->screen_base);
}
static struct fb_ops efxfb_ops = {
.owner = THIS_MODULE,
.fb_mmap = efxfb_mmap,
.fb_destroy = efxfb_destroy,
.fb_setcolreg = efxfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static int efxfb_parse_dt(struct platform_device *pdev,
struct efxfb_params *params)
{
struct device_node *np = pdev->dev.of_node;
int ret;
ret = of_property_read_u32(np, "width", &params->width);
if (ret) {
dev_err(&pdev->dev, "Can't parse width property\n");
return ret;
}
ret = of_property_read_u32(np, "height", &params->height);
if (ret) {
dev_err(&pdev->dev, "Can't parse height property\n");
return ret;
}
ret = of_property_read_u32(np, "stride", &params->stride);
if (ret) {
dev_err(&pdev->dev, "Can't parse stride property\n");
return ret;
}
return 0;
}
static void efxfb_set_fix(struct platform_device *pdev,
struct efxfb_params *params)
{
struct fb_info *info = platform_get_drvdata(pdev);
struct efxfb_par *par = info->par;
info->fix = efxfb_fix;
if (par->fb_transfer_mode == 1)
info->fix.smem_start = par->dma_display_buff_io;
else if (par->fb_transfer_mode == 2)
info->fix.smem_start = (uintptr_t)page_to_phys(vmalloc_to_page(par->display_buf));
info->fix.smem_len = par->fb_size;
info->fix.line_length = params->stride;
}
static void efxfb_set_var(struct platform_device *pdev,
struct efxfb_params *params)
{
struct efxfb_format format;
struct fb_info *info = platform_get_drvdata(pdev);
char format_name[] = "a8b8g8r8";
format.bits_per_pixel = 32;
format.red.offset = 0;
format.red.length = 8;
format.green.offset = 8;
format.green.length = 8;
format.blue.offset = 16;
format.blue.length = 8;
format.transp.offset = 24;
format.transp.length = 8;
info->var = efxfb_var;
info->var.xres = params->width;
info->var.yres = params->height;
info->var.xres_virtual = params->width;
info->var.yres_virtual = params->height;
info->var.bits_per_pixel = format.bits_per_pixel;
info->var.red = format.red;
info->var.green = format.green;
info->var.blue = format.blue;
info->var.transp = format.transp;
dev_info(&pdev->dev, "framebuffer at 0x%lx, 0x%x bytes, mapped to 0x%pK\n",
info->fix.smem_start, info->fix.smem_len,
info->screen_base);
dev_info(&pdev->dev, "format=%s, mode=%dx%dx%d, linelength=%d\n",
format_name,
info->var.xres, info->var.yres,
info->var.bits_per_pixel, info->fix.line_length);
}
static int efxfb_init_scatterlist_vmalloc(struct efxfb_par *par, void *buf_addr, enum dma_transfer_direction direction)
{
struct device *dev = &par->pdev->dev;
struct scatterlist *sg;
struct page **pages;
size_t segments = par->segments;
size_t i;
int ret;
if (!is_vmalloc_addr(buf_addr) || (uintptr_t)buf_addr & (PAGE_SIZE - 1)) {
dev_err(dev, "Buffer address is not vmalloc or not page-aligned\n");
return -EINVAL;
}
// Allocate page array
pages = kcalloc(segments, sizeof(*pages), GFP_KERNEL);
if (!pages) {
dev_err(dev, "Failed to allocate page array\n");
return -ENOMEM;
}
par->pages = pages;
// Allocate scatterlist
sg = kcalloc(segments, sizeof(*sg), GFP_KERNEL);
if (!sg) {
dev_err(dev, "Failed to allocate scatterlist\n");
ret = -ENOMEM;
goto err_free_pages;
}
sg_init_table(sg, segments);
par->sg = sg;
// Get pages for vmalloc buffer
for (i = 0; i < segments; i++) {
pages[i] = vmalloc_to_page(buf_addr + i * PAGE_SIZE);
if (!pages[i]) {
dev_err(dev, "Failed to get page for vmalloc buffer\n");
ret = -ENOMEM;
goto err_free_sg;
}
// Initialize and populate scatterlist
sg_set_page(&sg[i], pages[i], PAGE_SIZE, 0);
}
par->nents = dma_map_sg(dev, sg, segments, direction);
if (par->nents <= 0) {
dev_err(dev, "Failed to map scatterlist with vmalloc buffer\n");
ret = -EIO;
if (par->nents > 0) {
dma_unmap_sg(dev, sg, segments, direction);
}
goto err_free_sg;
}
dma_sync_sg_for_device(dev, sg, par->nents, direction);
return 0;
err_free_sg:
kfree(par->sg);
par->sg = NULL;
err_free_pages:
kfree(par->pages);
par->pages = NULL;
return ret;
}
static int efxfb_dma_init_vmalloc_sg_cyclic(struct efxfb_par *par)
{
struct device *dev = &par->pdev->dev;
struct dma_async_tx_descriptor *tx = NULL;
dma_cookie_t cookie;
int ret;
unsigned int flags = DMA_CTRL_REUSE;
par->display_buf = vmalloc(par->fb_size);
if (!par->display_buf) {
dev_err(dev, "Failed to allocate memory using vmalloc\n");
return -ENOMEM;
}
// Calculate number of pages
par->segments = DIV_ROUND_UP(par->fb_size, PAGE_SIZE);
ret = efxfb_init_scatterlist_vmalloc(par, par->display_buf, DMA_MEM_TO_DEV);
if (ret)
return ret;
par->chan = dma_request_chan(dev, "display");
if (!par->chan) {
dev_err(dev, "Failed to request DMA channel for display\n");
ret = PTR_ERR(par->chan);
goto err_unmap_sg;
}
tx = dmaengine_prep_slave_sg(par->chan, par->sg, par->nents, DMA_MEM_TO_DEV, flags);
if (!tx) {
dev_err(dev, "Failed to prepare DMA descriptor\n");
ret = -EIO;
goto err_release_chan;
}
cookie = dmaengine_submit(tx);
if (dma_submit_error(cookie)) {
dev_err(dev, "Failed to submit DMA transfer\n");
ret = -EIO;
goto err_release_chan;
}
dma_async_issue_pending(par->chan);
return 0;
err_release_chan:
dma_release_channel(par->chan);
par->chan = NULL;
err_unmap_sg:
dma_unmap_sg(dev, par->sg, par->nents, DMA_MEM_TO_DEV);
kfree(par->sg);
par->sg = NULL;
kfree(par->pages);
par->pages = NULL;
vfree(par->display_buf);
par->display_buf = NULL;
return ret;
}
static int efxfb_dma_init_kmalloc_sg_cyclic(struct efxfb_par *par)
{
struct device *dev = &par->pdev->dev;
struct dma_chan *chan;
struct dma_async_tx_descriptor *tx = NULL;
dma_cookie_t cookie;
int ret;
size_t period_len = par->params->stride;
unsigned long flags = 0;
par->display_buf = dma_alloc_coherent(dev, par->fb_size,
&par->dma_display_buff_io,
GFP_KERNEL);
if (!par->display_buf) {
dev_err(dev, "Failed to request memory region\n");
return -ENOMEM;
}
chan = dma_request_chan(dev, "display");
if (IS_ERR(chan)) {
ret = PTR_ERR(chan);
dev_err(dev, "Failed to request DMA channel: %d\n", ret);
chan = NULL;
goto err_free_buf;
}
par->chan = chan;
tx = dmaengine_prep_dma_cyclic(chan, par->dma_display_buff_io, par->fb_size,
period_len, DMA_MEM_TO_DEV, flags);
if (!tx) {
dev_err(dev, "Failed to prepare DMA transfer\n");
ret = -ENOMEM;
goto err_prep;
}
cookie = dmaengine_submit(tx);
if (dma_submit_error(cookie)) {
dev_err(dev, "Failed to submit DMA transfer\n");
ret = -EIO;
goto err_prep;
}
dev_info(dev, "Start DMA transfer for framebuffer\n");
dma_async_issue_pending(chan);
return 0;
err_prep:
dma_release_channel(par->chan);
par->chan = NULL;
err_free_buf:
dma_free_coherent(dev, par->fb_size, par->display_buf, par->dma_display_buff_io);
par->display_buf = NULL;
return ret;
}
static int efxfb_probe(struct platform_device *pdev)
{
int ret;
struct efxfb_params params;
struct fb_info *info;
struct efxfb_par *par;
struct resource *res;
struct device *dev = &pdev->dev;
if (pdev->dev.of_node)
ret = efxfb_parse_dt(pdev, &params);
else
return -ENODEV;
par = devm_kzalloc(&pdev->dev, sizeof(struct efxfb_par), GFP_KERNEL);
if (!par)
return -ENOMEM;
par->pdev = pdev;
par->params = &params;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "No memory resource\n");
return -EINVAL;
}
par->base = devm_ioremap_resource(dev, res);
info = framebuffer_alloc(sizeof(struct efxfb_par), &pdev->dev);
if (!info)
return -ENOMEM;
par->fb_size = params.stride * params.height;
if (!par->fb_size) {
dev_err(&pdev->dev, "Framebuffer size should not be 0\n");
ret = -EINVAL;
goto error_fb_release;
}
// Reset the display
writel(1, par->base + 0x4);
udelay(10);
writel(0, par->base + 0x4);
if (par->fb_size < EFX_MAX_FB_SIZE) {
par->fb_transfer_mode = 1;
ret = efxfb_dma_init_kmalloc_sg_cyclic(par);
info->screen_buffer = par->display_buf;
efxfb_ops.fb_mmap = NULL;
} else {
par->fb_transfer_mode = 2;
ret = efxfb_dma_init_vmalloc_sg_cyclic(par);
info->screen_base = (char __iomem *)par->display_buf;
}
if (ret < 0)
goto error_fb_release;
platform_set_drvdata(pdev, info);
info->par = par;
info->pseudo_palette = par->palette;
efxfb_set_fix(pdev, &params);
efxfb_set_var(pdev, &params);
info->fbops = &efxfb_ops;
info->flags = FBINFO_DEFAULT | FBINFO_MISC_FIRMWARE;
ret = register_framebuffer(info);
if (ret < 0) {
dev_err(&pdev->dev, "Unable to register efxfb: %d\n", ret);
goto error_fb_release;
}
dev_info(&pdev->dev, "fb%d: efxfb registerd!\n", info->node);
return 0;
error_fb_release:
framebuffer_release(info);
return ret;
}
static int efxfb_remove(struct platform_device *pdev)
{
struct fb_info *info = platform_get_drvdata(pdev);
struct efxfb_par *par = info->par;
struct dma_chan *chan = par->chan;
if (chan) {
dma_release_channel(chan);
chan = NULL;
}
if (par->fb_transfer_mode == 1) {
dma_free_coherent(&pdev->dev, par->fb_size, par->display_buf, par->dma_display_buff_io);
} else if (par->fb_transfer_mode == 2) {
dma_unmap_sg(&pdev->dev, par->sg, par->nents, DMA_MEM_TO_DEV);
kfree(par->sg);
par->sg = NULL;
kfree(par->pages);
par->pages = NULL;
vfree(par->display_buf);
par->display_buf = NULL;
}
unregister_framebuffer(info);
framebuffer_release(info);
return 0;
}
static const struct of_device_id efxfb_of_match[] = {
{ .compatible = "efx,efx-fb"},
{},
};
MODULE_DEVICE_TABLE(of, efxfb_of_match);
static struct platform_driver efxfb_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = efxfb_of_match,
},
.probe = efxfb_probe,
.remove = efxfb_remove,
};
static int __init efxfb_init(void)
{
struct device_node *np;
platform_driver_register(&efxfb_driver);
if (IS_ENABLED(CONFIG_OF_ADDRESS) && of_chosen) {
for_each_child_of_node(of_chosen, np) {
if (of_device_is_compatible(np, DRIVER_NAME))
of_platform_device_create(np, NULL, NULL);
}
}
return 0;
}
static void __exit efxfb_exit(void)
{
platform_driver_unregister(&efxfb_driver);
}
late_initcall(efxfb_init);
MODULE_AUTHOR("Alim Hussin <mnalim@efinixinc.com>");
MODULE_DESCRIPTION("Efinix framebuffer driver");
MODULE_LICENSE("GPL v2");

View File

@@ -351,6 +351,15 @@ config SL28CPLD_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called sl28cpld_wdt.
config EFINIX_WATCHDOG
tristate "Efinix Watchdog"
select WATCHDOG_CORE
help
Say Y here to include suport for the watchdog timer on Efinix boards.
To compile this driver as a module, choose M here: the module will be
called efx_wdt.
# ALPHA Architecture
# ARM Architecture

View File

@@ -227,3 +227,4 @@ obj-$(CONFIG_MENZ069_WATCHDOG) += menz69_wdt.o
obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o
obj-$(CONFIG_SL28CPLD_WATCHDOG) += sl28cpld_wdt.o
obj-$(CONFIG_EFINIX_WATCHDOG) += efx_wdt.o

298
drivers/watchdog/efx_wdt.c Normal file
View File

@@ -0,0 +1,298 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Support for watchdog on Efinix Inc SoC
*
* Copyright (C) 2025 Efinix Inc
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/watchdog.h>
#define DRIVER_NAME "efx-watchdog"
#define EFX_WDT_DEFAULT_TIME 60 /* in seconds */
#define EFX_WDT_MAX_TIME 65U /* in seconds */
#define EFX_WDT_RESET 0xAD68E70D /* Magic number to reset the watchdog */
#define EFX_WDT_UNLOCK 0x3C21B925 /* Magic number to unlock the watchdog */
#define EFX_WDT_LOCK 0x3C21B924 /* Magic number to lock the watchdog */
#define EFX_WDT_ENA_COUNTER_0 (1 << 0)
#define EFX_WDT_ENA_COUNTER_1 (1 << 1)
#define EFX_WDT_HEARTBEAT 0x00
#define EFX_WDT_ENABLE 0x04
#define EFX_WDT_DISABLED 0x08
#define EFX_WDT_PRESCALER 0x40
#define EFX_WDT_COUNTER_0 0x80
#define EFX_WDT_COUNTER_1 0x84
#define EFX_WDT_COUNTER_0_VAL 0xc0
#define EFX_WDT_COUNTER_1_VAL 0xc4
struct efx_wdt_dev {
void __iomem *base;
struct watchdog_device wdog;
struct clk *clk;
};
static unsigned timeout;
module_param(timeout, uint, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default="
__MODULE_STRING(EFX_WDT_DEFAULT_TIME) ")");
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot bbe sttopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static const struct watchdog_info efx_wdt_info = {
.identity = "efx-watchdog",
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
};
static const struct watchdog_info efx_wdt_pretimeout_info = {
.identity = "efx-watchdog-pretimeout",
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
WDIOF_PRETIMEOUT,
};
static void efx_wdt_unlock(struct efx_wdt_dev *wdev)
{
writel(EFX_WDT_UNLOCK, wdev->base + EFX_WDT_HEARTBEAT);
}
static void efx_wdt_lock(struct efx_wdt_dev *wdev)
{
writel(EFX_WDT_LOCK, wdev->base + EFX_WDT_HEARTBEAT);
}
static void efx_wdt_set_counter_limit(struct efx_wdt_dev *wdev, unsigned int limit, int counter)
{
unsigned int value;
int counter_reg;
if (counter == 0)
counter_reg = EFX_WDT_COUNTER_0;
else if (counter == 1)
counter_reg = EFX_WDT_COUNTER_1;
value = (limit * 1000) + 1;
efx_wdt_unlock(wdev);
writel(value, wdev->base + counter_reg);
efx_wdt_lock(wdev);
}
static int efx_wdt_start(struct watchdog_device *wdog)
{
struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog);
efx_wdt_unlock(wdev);
writel(EFX_WDT_ENA_COUNTER_0 | EFX_WDT_ENA_COUNTER_1 , wdev->base + EFX_WDT_ENABLE);
efx_wdt_lock(wdev);
set_bit(WDOG_HW_RUNNING | WDOG_ACTIVE, &wdog->status);
return 0;
}
static int efx_wdt_ping(struct watchdog_device *wdog)
{
struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog);
writel(EFX_WDT_RESET, wdev->base + EFX_WDT_HEARTBEAT);
return 0;
}
static int efx_wdt_stop(struct watchdog_device *wdog)
{
struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog);
efx_wdt_unlock(wdev);
writel(EFX_WDT_ENA_COUNTER_0 | EFX_WDT_ENA_COUNTER_1, wdev->base + EFX_WDT_DISABLED);
efx_wdt_lock(wdev);
set_bit(WDOG_STOP_ON_UNREGISTER, &wdog->status);
return 0;
}
static int efx_wdt_set_pretimeout(struct watchdog_device *wdog, unsigned int pretimeout)
{
struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog);
unsigned int actual;
actual = min(pretimeout, wdog->timeout);
wdog->pretimeout = actual;
efx_wdt_set_counter_limit(wdev, wdog->pretimeout, 0);
return 0;
}
static int efx_wdt_set_timeout(struct watchdog_device *wdog, unsigned int new_timeout)
{
struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog);
unsigned int actual;
actual = min(new_timeout, EFX_WDT_MAX_TIME);
wdog->timeout = actual;
efx_wdt_set_counter_limit(wdev, wdog->timeout, 1);
efx_wdt_set_pretimeout(wdog, wdog->timeout / 2);
return 0;
}
static unsigned int efx_wdt_get_timeleft(struct watchdog_device *wdog)
{
struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog);
unsigned int value;
value = readl(wdev->base + EFX_WDT_COUNTER_1_VAL);
return (wdog->timeout * 1000) - value;
}
static int efx_wdt_restart(struct watchdog_device *wdog, unsigned long action,
void *data)
{
struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog);
efx_wdt_stop(wdog);
efx_wdt_ping(wdog);
efx_wdt_set_counter_limit(wdev, wdog->min_timeout, 1);
efx_wdt_unlock(wdev);
writel(EFX_WDT_ENA_COUNTER_1, wdev->base + EFX_WDT_ENABLE);
efx_wdt_lock(wdev);
return 0;
}
static irqreturn_t efx_wdt_isr(int irq, void *dev)
{
struct watchdog_device *wdog = (struct watchdog_device *)dev;
struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog);
writel(~(EFX_WDT_ENA_COUNTER_0), wdev->base + EFX_WDT_ENABLE);
watchdog_notify_pretimeout(wdog);
return IRQ_HANDLED;
}
static const struct watchdog_ops efx_wdt_ops = {
.start = efx_wdt_start,
.stop = efx_wdt_stop,
.ping = efx_wdt_ping,
.set_timeout = efx_wdt_set_timeout,
.set_pretimeout = efx_wdt_set_pretimeout,
.get_timeleft = efx_wdt_get_timeleft,
.restart = efx_wdt_restart,
};
static int __init efx_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct efx_wdt_dev *wdev;
struct watchdog_device *wdog;
int irq, ret;
unsigned int clk_rate, prescaler;
wdev = devm_kzalloc(dev, sizeof(*wdev), GFP_KERNEL);
if (!wdev)
return -ENOMEM;
wdev->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(wdev->base))
return PTR_ERR(wdev->base);
wdev->clk = devm_clk_get(dev, NULL);
if (IS_ERR(wdev->clk)) {
dev_err(dev, "can't get watchdog clock\n");
return PTR_ERR(wdev->clk);
}
wdog = &wdev->wdog;
wdog->info = &efx_wdt_info;
wdog->ops = &efx_wdt_ops;
wdog->min_timeout = 1;
wdog->timeout = EFX_WDT_DEFAULT_TIME;
wdog->max_hw_heartbeat_ms = EFX_WDT_MAX_TIME * 1000;
wdog->parent = dev;
/* Set the prescaler */
clk_rate = clk_get_rate(wdev->clk);
if (!clk_rate) {
dev_err(dev, "Missing 'clock-frequency' for watchdog in device tree.\n");
return -EINVAL;
}
if (clk_rate == 0) {
dev_err(dev, "'clock-frequency' for watchdog cannot be 0\n");
return -EINVAL;
}
prescaler = (clk_rate / 1000) - 1;
writel(prescaler, wdev->base + EFX_WDT_PRESCALER);
irq = platform_get_irq(pdev, 0);
if (irq > 0) {
ret = devm_request_irq(&pdev->dev, irq, efx_wdt_isr, 0, DRIVER_NAME, wdog);
if (ret)
return ret;
wdog->info = &efx_wdt_pretimeout_info;
}
platform_set_drvdata(pdev, wdog);
watchdog_set_drvdata(wdog, wdev);
watchdog_set_nowayout(wdog, nowayout);
watchdog_set_restart_priority(wdog, 128);
watchdog_init_timeout(wdog, timeout, dev);
efx_wdt_set_timeout(wdog, wdog->timeout);
efx_wdt_set_pretimeout(wdog, wdog->timeout / 2);
efx_wdt_stop(wdog);
ret = devm_watchdog_register_device(dev, wdog);
if (ret) {
dev_err(dev, "Failed to register watchdog driver\n");
return ret;
}
dev_info(dev, "Successfully register watchdog driver\n");
efx_wdt_lock(wdev);
return 0;
}
static void efx_wdt_shutdown(struct platform_device *pdev)
{
struct watchdog_device *wdog = (struct watchdog_device *)platform_get_drvdata(pdev);
struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog);
efx_wdt_stop(wdog);
efx_wdt_ping(wdog);
efx_wdt_set_counter_limit(wdev, wdog->min_timeout, 1);
efx_wdt_unlock(wdev);
writel(EFX_WDT_ENA_COUNTER_1, wdev->base + EFX_WDT_ENABLE);
efx_wdt_lock(wdev);
}
static const struct of_device_id efx_wdt_ids[] = {
{ .compatible = "efx,efx-wdt" },
{ }
};
MODULE_DEVICE_TABLE(of, efx_wdt_ids);
static struct platform_driver efx_wdt_driver = {
.shutdown = efx_wdt_shutdown,
.driver = {
.name = DRIVER_NAME,
.of_match_table = efx_wdt_ids,
}
};
module_platform_driver_probe(efx_wdt_driver, efx_wdt_probe);
MODULE_AUTHOR("Mohamad Noor Alim Hussin <mnalim@efinixinc.com>");
MODULE_DESCRIPTION("Watchdog driver for Efinix SoC");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);