Path: blob/master/drivers/dma/dw-edma/dw-edma-v0-core.c
26282 views
// SPDX-License-Identifier: GPL-2.01/*2* Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.3* Synopsys DesignWare eDMA v0 core4*5* Author: Gustavo Pimentel <[email protected]>6*/78#include <linux/bitfield.h>9#include <linux/irqreturn.h>10#include <linux/io-64-nonatomic-lo-hi.h>1112#include "dw-edma-core.h"13#include "dw-edma-v0-core.h"14#include "dw-edma-v0-regs.h"15#include "dw-edma-v0-debugfs.h"1617enum dw_edma_control {18DW_EDMA_V0_CB = BIT(0),19DW_EDMA_V0_TCB = BIT(1),20DW_EDMA_V0_LLP = BIT(2),21DW_EDMA_V0_LIE = BIT(3),22DW_EDMA_V0_RIE = BIT(4),23DW_EDMA_V0_CCS = BIT(8),24DW_EDMA_V0_LLE = BIT(9),25};2627static inline struct dw_edma_v0_regs __iomem *__dw_regs(struct dw_edma *dw)28{29return dw->chip->reg_base;30}3132#define SET_32(dw, name, value) \33writel(value, &(__dw_regs(dw)->name))3435#define GET_32(dw, name) \36readl(&(__dw_regs(dw)->name))3738#define SET_RW_32(dw, dir, name, value) \39do { \40if ((dir) == EDMA_DIR_WRITE) \41SET_32(dw, wr_##name, value); \42else \43SET_32(dw, rd_##name, value); \44} while (0)4546#define GET_RW_32(dw, dir, name) \47((dir) == EDMA_DIR_WRITE \48? GET_32(dw, wr_##name) \49: GET_32(dw, rd_##name))5051#define SET_BOTH_32(dw, name, value) \52do { \53SET_32(dw, wr_##name, value); \54SET_32(dw, rd_##name, value); \55} while (0)5657#define SET_64(dw, name, value) \58writeq(value, &(__dw_regs(dw)->name))5960#define GET_64(dw, name) \61readq(&(__dw_regs(dw)->name))6263#define SET_RW_64(dw, dir, name, value) \64do { \65if ((dir) == EDMA_DIR_WRITE) \66SET_64(dw, wr_##name, value); \67else \68SET_64(dw, rd_##name, value); \69} while (0)7071#define GET_RW_64(dw, dir, name) \72((dir) == EDMA_DIR_WRITE \73? GET_64(dw, wr_##name) \74: GET_64(dw, rd_##name))7576#define SET_BOTH_64(dw, name, value) \77do { \78SET_64(dw, wr_##name, value); \79SET_64(dw, rd_##name, value); \80} while (0)8182#define SET_COMPAT(dw, name, value) \83writel(value, &(__dw_regs(dw)->type.unroll.name))8485#define SET_RW_COMPAT(dw, dir, name, value) \86do { \87if ((dir) == EDMA_DIR_WRITE) \88SET_COMPAT(dw, wr_##name, value); \89else \90SET_COMPAT(dw, rd_##name, value); \91} while (0)9293static inline struct dw_edma_v0_ch_regs __iomem *94__dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch)95{96if (dw->chip->mf == EDMA_MF_EDMA_LEGACY)97return &(__dw_regs(dw)->type.legacy.ch);9899if (dir == EDMA_DIR_WRITE)100return &__dw_regs(dw)->type.unroll.ch[ch].wr;101102return &__dw_regs(dw)->type.unroll.ch[ch].rd;103}104105static inline void writel_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch,106u32 value, void __iomem *addr)107{108if (dw->chip->mf == EDMA_MF_EDMA_LEGACY) {109u32 viewport_sel;110unsigned long flags;111112raw_spin_lock_irqsave(&dw->lock, flags);113114viewport_sel = FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch);115if (dir == EDMA_DIR_READ)116viewport_sel |= BIT(31);117118writel(viewport_sel,119&(__dw_regs(dw)->type.legacy.viewport_sel));120writel(value, addr);121122raw_spin_unlock_irqrestore(&dw->lock, flags);123} else {124writel(value, addr);125}126}127128static inline u32 readl_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch,129const void __iomem *addr)130{131u32 value;132133if (dw->chip->mf == EDMA_MF_EDMA_LEGACY) {134u32 viewport_sel;135unsigned long flags;136137raw_spin_lock_irqsave(&dw->lock, flags);138139viewport_sel = FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch);140if (dir == EDMA_DIR_READ)141viewport_sel |= BIT(31);142143writel(viewport_sel,144&(__dw_regs(dw)->type.legacy.viewport_sel));145value = readl(addr);146147raw_spin_unlock_irqrestore(&dw->lock, flags);148} else {149value = readl(addr);150}151152return value;153}154155#define SET_CH_32(dw, dir, ch, name, value) \156writel_ch(dw, dir, ch, value, &(__dw_ch_regs(dw, dir, ch)->name))157158#define GET_CH_32(dw, dir, ch, name) \159readl_ch(dw, dir, ch, &(__dw_ch_regs(dw, dir, ch)->name))160161/* eDMA management callbacks */162static void dw_edma_v0_core_off(struct dw_edma *dw)163{164SET_BOTH_32(dw, int_mask,165EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK);166SET_BOTH_32(dw, int_clear,167EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK);168SET_BOTH_32(dw, engine_en, 0);169}170171static u16 dw_edma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)172{173u32 num_ch;174175if (dir == EDMA_DIR_WRITE)176num_ch = FIELD_GET(EDMA_V0_WRITE_CH_COUNT_MASK,177GET_32(dw, ctrl));178else179num_ch = FIELD_GET(EDMA_V0_READ_CH_COUNT_MASK,180GET_32(dw, ctrl));181182if (num_ch > EDMA_V0_MAX_NR_CH)183num_ch = EDMA_V0_MAX_NR_CH;184185return (u16)num_ch;186}187188static enum dma_status dw_edma_v0_core_ch_status(struct dw_edma_chan *chan)189{190struct dw_edma *dw = chan->dw;191u32 tmp;192193tmp = FIELD_GET(EDMA_V0_CH_STATUS_MASK,194GET_CH_32(dw, chan->dir, chan->id, ch_control1));195196if (tmp == 1)197return DMA_IN_PROGRESS;198else if (tmp == 3)199return DMA_COMPLETE;200else201return DMA_ERROR;202}203204static void dw_edma_v0_core_clear_done_int(struct dw_edma_chan *chan)205{206struct dw_edma *dw = chan->dw;207208SET_RW_32(dw, chan->dir, int_clear,209FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)));210}211212static void dw_edma_v0_core_clear_abort_int(struct dw_edma_chan *chan)213{214struct dw_edma *dw = chan->dw;215216SET_RW_32(dw, chan->dir, int_clear,217FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)));218}219220static u32 dw_edma_v0_core_status_done_int(struct dw_edma *dw, enum dw_edma_dir dir)221{222return FIELD_GET(EDMA_V0_DONE_INT_MASK,223GET_RW_32(dw, dir, int_status));224}225226static u32 dw_edma_v0_core_status_abort_int(struct dw_edma *dw, enum dw_edma_dir dir)227{228return FIELD_GET(EDMA_V0_ABORT_INT_MASK,229GET_RW_32(dw, dir, int_status));230}231232static irqreturn_t233dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,234dw_edma_handler_t done, dw_edma_handler_t abort)235{236struct dw_edma *dw = dw_irq->dw;237unsigned long total, pos, val;238irqreturn_t ret = IRQ_NONE;239struct dw_edma_chan *chan;240unsigned long off;241u32 mask;242243if (dir == EDMA_DIR_WRITE) {244total = dw->wr_ch_cnt;245off = 0;246mask = dw_irq->wr_mask;247} else {248total = dw->rd_ch_cnt;249off = dw->wr_ch_cnt;250mask = dw_irq->rd_mask;251}252253val = dw_edma_v0_core_status_done_int(dw, dir);254val &= mask;255for_each_set_bit(pos, &val, total) {256chan = &dw->chan[pos + off];257258dw_edma_v0_core_clear_done_int(chan);259done(chan);260261ret = IRQ_HANDLED;262}263264val = dw_edma_v0_core_status_abort_int(dw, dir);265val &= mask;266for_each_set_bit(pos, &val, total) {267chan = &dw->chan[pos + off];268269dw_edma_v0_core_clear_abort_int(chan);270abort(chan);271272ret = IRQ_HANDLED;273}274275return ret;276}277278static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,279u32 control, u32 size, u64 sar, u64 dar)280{281ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);282283if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {284struct dw_edma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;285286lli->control = control;287lli->transfer_size = size;288lli->sar.reg = sar;289lli->dar.reg = dar;290} else {291struct dw_edma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;292293writel(control, &lli->control);294writel(size, &lli->transfer_size);295writeq(sar, &lli->sar.reg);296writeq(dar, &lli->dar.reg);297}298}299300static void dw_edma_v0_write_ll_link(struct dw_edma_chunk *chunk,301int i, u32 control, u64 pointer)302{303ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);304305if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {306struct dw_edma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;307308llp->control = control;309llp->llp.reg = pointer;310} else {311struct dw_edma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;312313writel(control, &llp->control);314writeq(pointer, &llp->llp.reg);315}316}317318static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk)319{320struct dw_edma_burst *child;321struct dw_edma_chan *chan = chunk->chan;322u32 control = 0, i = 0;323int j;324325if (chunk->cb)326control = DW_EDMA_V0_CB;327328j = chunk->bursts_alloc;329list_for_each_entry(child, &chunk->burst->list, list) {330j--;331if (!j) {332control |= DW_EDMA_V0_LIE;333if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))334control |= DW_EDMA_V0_RIE;335}336337dw_edma_v0_write_ll_data(chunk, i++, control, child->sz,338child->sar, child->dar);339}340341control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB;342if (!chunk->cb)343control |= DW_EDMA_V0_CB;344345dw_edma_v0_write_ll_link(chunk, i, control, chunk->ll_region.paddr);346}347348static void dw_edma_v0_sync_ll_data(struct dw_edma_chunk *chunk)349{350/*351* In case of remote eDMA engine setup, the DW PCIe RP/EP internal352* configuration registers and application memory are normally accessed353* over different buses. Ensure LL-data reaches the memory before the354* doorbell register is toggled by issuing the dummy-read from the remote355* LL memory in a hope that the MRd TLP will return only after the356* last MWr TLP is completed357*/358if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))359readl(chunk->ll_region.vaddr.io);360}361362static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)363{364struct dw_edma_chan *chan = chunk->chan;365struct dw_edma *dw = chan->dw;366u32 tmp;367368dw_edma_v0_core_write_chunk(chunk);369370if (first) {371/* Enable engine */372SET_RW_32(dw, chan->dir, engine_en, BIT(0));373if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) {374switch (chan->id) {375case 0:376SET_RW_COMPAT(dw, chan->dir, ch0_pwr_en,377BIT(0));378break;379case 1:380SET_RW_COMPAT(dw, chan->dir, ch1_pwr_en,381BIT(0));382break;383case 2:384SET_RW_COMPAT(dw, chan->dir, ch2_pwr_en,385BIT(0));386break;387case 3:388SET_RW_COMPAT(dw, chan->dir, ch3_pwr_en,389BIT(0));390break;391case 4:392SET_RW_COMPAT(dw, chan->dir, ch4_pwr_en,393BIT(0));394break;395case 5:396SET_RW_COMPAT(dw, chan->dir, ch5_pwr_en,397BIT(0));398break;399case 6:400SET_RW_COMPAT(dw, chan->dir, ch6_pwr_en,401BIT(0));402break;403case 7:404SET_RW_COMPAT(dw, chan->dir, ch7_pwr_en,405BIT(0));406break;407}408}409/* Interrupt unmask - done, abort */410tmp = GET_RW_32(dw, chan->dir, int_mask);411tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));412tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));413SET_RW_32(dw, chan->dir, int_mask, tmp);414/* Linked list error */415tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);416tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));417SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);418/* Channel control */419SET_CH_32(dw, chan->dir, chan->id, ch_control1,420(DW_EDMA_V0_CCS | DW_EDMA_V0_LLE));421/* Linked list */422/* llp is not aligned on 64bit -> keep 32bit accesses */423SET_CH_32(dw, chan->dir, chan->id, llp.lsb,424lower_32_bits(chunk->ll_region.paddr));425SET_CH_32(dw, chan->dir, chan->id, llp.msb,426upper_32_bits(chunk->ll_region.paddr));427}428429dw_edma_v0_sync_ll_data(chunk);430431/* Doorbell */432SET_RW_32(dw, chan->dir, doorbell,433FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id));434}435436static void dw_edma_v0_core_ch_config(struct dw_edma_chan *chan)437{438struct dw_edma *dw = chan->dw;439u32 tmp = 0;440441/* MSI done addr - low, high */442SET_RW_32(dw, chan->dir, done_imwr.lsb, chan->msi.address_lo);443SET_RW_32(dw, chan->dir, done_imwr.msb, chan->msi.address_hi);444/* MSI abort addr - low, high */445SET_RW_32(dw, chan->dir, abort_imwr.lsb, chan->msi.address_lo);446SET_RW_32(dw, chan->dir, abort_imwr.msb, chan->msi.address_hi);447/* MSI data - low, high */448switch (chan->id) {449case 0:450case 1:451tmp = GET_RW_32(dw, chan->dir, ch01_imwr_data);452break;453454case 2:455case 3:456tmp = GET_RW_32(dw, chan->dir, ch23_imwr_data);457break;458459case 4:460case 5:461tmp = GET_RW_32(dw, chan->dir, ch45_imwr_data);462break;463464case 6:465case 7:466tmp = GET_RW_32(dw, chan->dir, ch67_imwr_data);467break;468}469470if (chan->id & BIT(0)) {471/* Channel odd {1, 3, 5, 7} */472tmp &= EDMA_V0_CH_EVEN_MSI_DATA_MASK;473tmp |= FIELD_PREP(EDMA_V0_CH_ODD_MSI_DATA_MASK,474chan->msi.data);475} else {476/* Channel even {0, 2, 4, 6} */477tmp &= EDMA_V0_CH_ODD_MSI_DATA_MASK;478tmp |= FIELD_PREP(EDMA_V0_CH_EVEN_MSI_DATA_MASK,479chan->msi.data);480}481482switch (chan->id) {483case 0:484case 1:485SET_RW_32(dw, chan->dir, ch01_imwr_data, tmp);486break;487488case 2:489case 3:490SET_RW_32(dw, chan->dir, ch23_imwr_data, tmp);491break;492493case 4:494case 5:495SET_RW_32(dw, chan->dir, ch45_imwr_data, tmp);496break;497498case 6:499case 7:500SET_RW_32(dw, chan->dir, ch67_imwr_data, tmp);501break;502}503}504505/* eDMA debugfs callbacks */506static void dw_edma_v0_core_debugfs_on(struct dw_edma *dw)507{508dw_edma_v0_debugfs_on(dw);509}510511static const struct dw_edma_core_ops dw_edma_v0_core = {512.off = dw_edma_v0_core_off,513.ch_count = dw_edma_v0_core_ch_count,514.ch_status = dw_edma_v0_core_ch_status,515.handle_int = dw_edma_v0_core_handle_int,516.start = dw_edma_v0_core_start,517.ch_config = dw_edma_v0_core_ch_config,518.debugfs_on = dw_edma_v0_core_debugfs_on,519};520521void dw_edma_v0_core_register(struct dw_edma *dw)522{523dw->core = &dw_edma_v0_core;524}525526527