Compare commits
10 Commits
8d7d404de2
...
9074d7744f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9074d7744f | ||
|
|
334eb3ba15 | ||
|
|
d7b360824e | ||
|
|
7c649c3ce5 | ||
|
|
f0ec6f6955 | ||
|
|
d31b653b2f | ||
|
|
b6b404e6d1 | ||
|
|
89d4b5521f | ||
|
|
6f082dfd7a | ||
|
|
0debccc414 |
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
889
drivers/dma/efx_dma.c
Normal 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");
|
||||
@@ -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.
|
||||
|
||||
@@ -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
304
drivers/mmc/host/efx_emmc.h
Normal 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
|
||||
975
drivers/mmc/host/efx_emmc_core.c
Normal file
975
drivers/mmc/host/efx_emmc_core.c
Normal 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;
|
||||
}
|
||||
209
drivers/mmc/host/efx_emmc_dma.c
Normal file
209
drivers/mmc/host/efx_emmc_dma.c
Normal 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);
|
||||
}
|
||||
370
drivers/mmc/host/efx_emmc_platform.c
Normal file
370
drivers/mmc/host/efx_emmc_platform.c
Normal 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");
|
||||
459
drivers/mmc/host/efx_emmc_tuning.c
Normal file
459
drivers/mmc/host/efx_emmc_tuning.c
Normal 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
386
drivers/mmc/host/efx_sdio.h
Normal 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__ */
|
||||
1097
drivers/mmc/host/efx_sdio_core.c
Normal file
1097
drivers/mmc/host/efx_sdio_core.c
Normal file
File diff suppressed because it is too large
Load Diff
227
drivers/mmc/host/efx_sdio_dma.c
Normal file
227
drivers/mmc/host/efx_sdio_dma.c
Normal 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);
|
||||
}
|
||||
426
drivers/mmc/host/efx_sdio_platform.c
Normal file
426
drivers/mmc/host/efx_sdio_platform.c
Normal 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");
|
||||
681
drivers/mmc/host/efx_sdio_tuning.c
Normal file
681
drivers/mmc/host/efx_sdio_tuning.c
Normal 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;
|
||||
}
|
||||
@@ -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) },
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
555
drivers/video/fbdev/fb_efx.c
Normal file
555
drivers/video/fbdev/fb_efx.c
Normal 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", ¶ms->width);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Can't parse width property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "height", ¶ms->height);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Can't parse height property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "stride", ¶ms->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, ¶ms);
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
par = devm_kzalloc(&pdev->dev, sizeof(struct efxfb_par), GFP_KERNEL);
|
||||
if (!par)
|
||||
return -ENOMEM;
|
||||
|
||||
par->pdev = pdev;
|
||||
par->params = ¶ms;
|
||||
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, ¶ms);
|
||||
efxfb_set_var(pdev, ¶ms);
|
||||
|
||||
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");
|
||||
@@ -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
|
||||
|
||||
@@ -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
298
drivers/watchdog/efx_wdt.c
Normal 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);
|
||||
Reference in New Issue
Block a user