Index: linux-2.6.33/drivers/dma/Kconfig =================================================================== --- linux-2.6.33.orig/drivers/dma/Kconfig +++ linux-2.6.33/drivers/dma/Kconfig @@ -20,6 +20,37 @@ comment "DMA Devices" config ASYNC_TX_DISABLE_CHANNEL_SWITCH bool +config INTEL_LNW_DMAC1 + bool "Intel MID DMA support for LPE DMA" + depends on PCI && X86 && (SND_INTEL_SST||SND_INTEL_LPE) + select DMA_ENGINE + help + Enable support for the Intel(R) MID DMA1 engine present + in Intel MID chipsets. + + Say Y here if you have such a chipset. + + If unsure, say N. + +config INTEL_LNW_DMAC2 + bool "Intel MID DMA support for SC DMA" + depends on PCI && X86 + select DMA_ENGINE + help + Enable support for the Intel(R) MID DMA2 engine present + in Intel MID chipsets. + + Say Y here if you have such a chipset. + + If unsure, say N. + +config LNW_DMA_DEBUG + bool "LNW DMA Debugging Enable" + depends on INTEL_LNW_DMAC1 || INTEL_LNW_DMAC2 + default N + help + Enable logging in the LNW DMA drivers + config INTEL_IOATDMA tristate "Intel I/OAT DMA support" depends on PCI && X86 Index: linux-2.6.33/drivers/dma/Makefile =================================================================== --- linux-2.6.33.orig/drivers/dma/Makefile +++ linux-2.6.33/drivers/dma/Makefile @@ -1,5 +1,7 @@ obj-$(CONFIG_DMA_ENGINE) += dmaengine.o obj-$(CONFIG_NET_DMA) += iovlock.o +obj-$(CONFIG_INTEL_LNW_DMAC2) += lnw_dmac2.o +obj-$(CONFIG_INTEL_LNW_DMAC1) += lnw_dmac1.o obj-$(CONFIG_DMATEST) += dmatest.o obj-$(CONFIG_INTEL_IOATDMA) += ioat/ obj-$(CONFIG_INTEL_IOP_ADMA) += iop-adma.o Index: linux-2.6.33/drivers/dma/lnw_dma_regs.h =================================================================== --- /dev/null +++ linux-2.6.33/drivers/dma/lnw_dma_regs.h @@ -0,0 +1,176 @@ +/* + * lnw_dma.c - Intel Langwell DMA Drivers + * + * Copyright (C) 2008-09 Intel Corp + * Author: Vinod Koul + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#ifndef __LNW_DMA_REGS_H__ +#define __LNW_DMA_REGS_H__ + +#include +#include +#include + +#define LNW_DMA_DRIVER_VERSION "0.3.1" + +#define DMA_DEBUG + +#define REG_BIT0 0x00000001 +#define REG_BIT8 0x00000100 + +#define UNMASK_INTR_REG(chan_num) \ + ((REG_BIT0 << chan_num) | (REG_BIT8 << chan_num)) +#define MASK_INTR_REG(chan_num) (REG_BIT8 << chan_num) + +#define ENABLE_CHANNEL(chan_num) \ + ((REG_BIT0 << chan_num) | (REG_BIT8 << chan_num)) + +#define DESCS_PER_CHANNEL 16 +/*DMA Registers*/ +/*registers associated with channel programming*/ +#define DMA_REG_SIZE 0x400 +#define DMA_CH_SIZE 0x58 + +/*CH X REG = (DMA_CH_SIZE)*CH_NO + REG*/ +#define SAR 0x00 /* Source Address Register*/ +#define DAR 0x08 /* Destination Address Register*/ +#define CTL_LOW 0x18 /* Control Register*/ +#define CTL_HIGH 0x1C /* Control Register*/ +#define CFG_LOW 0x40 /* Configuration Register Low*/ +#define CFG_HIGH 0x44 /* Configuration Register high*/ + +#define STATUS_TFR 0x2E8 +#define STATUS_BLOCK 0x2F0 +#define STATUS_ERR 0x308 + +#define RAW_TFR 0x2C0 +#define RAW_BLOCK 0x2C8 +#define RAW_ERR 0x2E0 + +#define MASK_TFR 0x310 +#define MASK_BLOCK 0x318 +#define MASK_SRC_TRAN 0x320 +#define MASK_DST_TRAN 0x328 +#define MASK_ERR 0x330 + +#define CLEAR_TFR 0x338 +#define CLEAR_BLOCK 0x340 +#define CLEAR_SRC_TRAN 0x348 +#define CLEAR_DST_TRAN 0x350 +#define CLEAR_ERR 0x358 + +#define INTR_STATUS 0x360 +#define DMA_CFG 0x398 +#define DMA_CHAN_EN 0x3A0 + +/** + * struct lnw_dma_chan - internal representation of a DMA channel + */ +struct lnw_dma_chan { + struct dma_chan chan; + void __iomem *ch_regs; + void __iomem *dma_base; + int ch_id; + spinlock_t lock; + dma_cookie_t completed; + struct list_head active_list; + struct list_head queue; + struct list_head free_list; + struct lnw_dma_slave *slave; + unsigned int descs_allocated; + struct lnwdma_device *dma; + bool in_use; +}; +static inline struct lnw_dma_chan *to_lnw_dma_chan(struct dma_chan *chan) +{ + return container_of(chan, struct lnw_dma_chan, chan); +} + +/** + * struct lnwdma_device - internal representation of a DMA device + * @pdev: PCI device + * @dma_base: MMIO register space base address of DMA + * @lpe_base: MMIO register space base address of LPE + * @dma_pool: for allocating DMA descriptors + * @common: embedded struct dma_device + * @idx: per channel data + */ +struct lnwdma_device { + struct pci_dev *pdev; + void __iomem *dma_base; + struct pci_pool *dma_pool; + struct dma_device common; + struct tasklet_struct tasklet; + struct lnw_dma_chan ch[MAX_CHAN]; +}; + +static inline struct lnwdma_device *to_lnwdma_device(struct dma_device *common) +{ + return container_of(common, struct lnwdma_device, common); +} + +struct lnw_dma_desc { + void __iomem *block; /*ch ptr*/ + struct list_head desc_node; + struct dma_async_tx_descriptor txd; + size_t len; + dma_addr_t sar; + dma_addr_t dar; + u32 cfg_hi; + u32 cfg_lo; + u32 ctl_lo; + u32 ctl_hi; + dma_addr_t next; + enum dma_data_direction dirn; + enum dma_status status; + dma_async_tx_callback callback; + void *callback_param; + enum lnw_dma_width width; /*width of DMA txn*/ + enum lnw_dma_mode cfg_mode; /*mode configuration*/ + +}; + +static inline int test_ch_en(void __iomem *dma, u32 ch_no) +{ + u32 en_reg = ioread32(dma + DMA_CHAN_EN); + return (en_reg >> ch_no) & 0x1; +} + +static inline struct lnw_dma_desc *to_lnw_dma_desc + (struct dma_async_tx_descriptor *txd) +{ + return container_of(txd, struct lnw_dma_desc, txd); +} + +#define _dma_printk(level, format, arg...) \ + printk(level "LNW_DMA: %s %d " format, __func__, __LINE__, ## arg) + +#ifdef CONFIG_LNW_DMA_DEBUG +#define dma_dbg(format, arg...) _dma_printk(KERN_DEBUG, "DBG " format , ## arg) +#else +#define dma_dbg(format, arg...) do {} while (0); +#endif + +#define dma_err(format, arg...) _dma_printk(KERN_ERR, "ERR " format , ## arg) +#define dma_info(format, arg...) \ + _dma_printk(KERN_INFO , "INFO " format , ## arg) + +#endif /*__LNW_DMA_REGS_H__*/ Index: linux-2.6.33/drivers/dma/lnw_dmac1.c =================================================================== --- /dev/null +++ linux-2.6.33/drivers/dma/lnw_dmac1.c @@ -0,0 +1,957 @@ +/* + * lnw_dmac1.c - Intel Langwell DMA Drivers + * + * Copyright (C) 2008-09 Intel Corp + * Authhor: Vinod Koul + * The driver design is based on dw_dmac driver + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#include +#include +#include +#include +#include +#include + +#define MAX_CHAN 2 +#include "lnw_dma_regs.h" + +MODULE_AUTHOR("Vinod Koul "); +MODULE_DESCRIPTION("Intel (R) Moorestown Langwell DMAC1 Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(LNW_DMA_DRIVER_VERSION); + +#define DMA_CH0 6 +#define DMA_CH1 7 +#define CH_BLOCK_SIZE 4095 + +static int __devinit lnw_dma1_probe(struct pci_dev *pdev, + const struct pci_device_id *id); +static void __devexit lnw_dma1_remove(struct pci_dev *pdev); +static void enable_dma1_interrupt(struct lnw_dma_chan *lnwc); +static void disable_dma1_interrupt(struct lnw_dma_chan *lnwc); + +struct lnw_device { + struct pci_dev *pdev; + void __iomem *dma_base; + struct lnwdma_device *dma; +}; + +/*CH dep code, if ch no's mapping changes only change here*/ +static int get_ch_id(int index) +{ + if (index == 0) + return DMA_CH0; + else if (index == 1) + return DMA_CH1; + else + return -1; +} + +static int get_ch_index(int ch_id) +{ + if (ch_id == DMA_CH0) + return 0; + if (ch_id == DMA_CH1) + return 1; + else + return -1; +} + +static int get_ch_num(int *status) +{ + if (*status & (1 << DMA_CH0)) { + *status = *status & (~(1 << DMA_CH0)); + return DMA_CH0; + } else if (*status & (1 << DMA_CH1)) { + *status = *status & (~(1 << DMA_CH1)); + return DMA_CH1; + } else + return -1; +} + +static int get_block_ts(int len, int tx_width) +{ + int byte_width = 0, block_ts = 0; + + switch (tx_width) { + case LNW_DMA_WIDTH_8BIT: + byte_width = 1; + break; + case LNW_DMA_WIDTH_16BIT: + byte_width = 2; + break; + case LNW_DMA_WIDTH_32BIT: + default: + byte_width = 4; + break; + } + + block_ts = len/byte_width; + if (block_ts > CH_BLOCK_SIZE) + block_ts = 0xFFFF; + return block_ts; +} + +static struct lnw_dma_desc *lnwc_desc_get1(struct lnw_dma_chan *lnwc) +{ + struct lnw_dma_desc *desc, *_desc; + struct lnw_dma_desc *ret = NULL; + + dma_dbg("called \n"); + spin_lock_bh(&lnwc->lock); + list_for_each_entry_safe(desc, _desc, &lnwc->free_list, desc_node) { + if (async_tx_test_ack(&desc->txd)) { + list_del(&desc->desc_node); + ret = desc; + dma_dbg("got free desc \n"); + break; + } + } + spin_unlock_bh(&lnwc->lock); + return ret; +} + + +static void lnwc_desc_put1(struct lnw_dma_chan *lnwc, struct lnw_dma_desc *desc) +{ + if (desc) { + spin_lock_bh(&lnwc->lock); + list_add_tail(&desc->desc_node, &lnwc->free_list); + spin_unlock_bh(&lnwc->lock); + } +} + +/* Called with dwc->lock held and bh disabled */ +static void lnwc_dostart1(struct lnw_dma_chan *lnwc, struct lnw_dma_desc *first) +{ + struct lnwdma_device *lnw = to_lnwdma_device(lnwc->chan.device); + + dma_dbg("called \n"); + /* ASSERT: channel is idle */ + if (lnwc->in_use && test_ch_en(lnwc->dma_base, lnwc->ch_id)) { + /*error*/ + dma_err("channel is busy \n"); + /* The tasklet will hopefully advance the queue... */ + return; + } + + /*write registers and en*/ + iowrite32(first->sar, lnwc->ch_regs + SAR); + iowrite32(first->dar, lnwc->ch_regs + DAR); + iowrite32(first->cfg_hi, lnwc->ch_regs + CFG_HIGH); + iowrite32(first->cfg_lo, lnwc->ch_regs + CFG_LOW); + iowrite32(first->ctl_lo, lnwc->ch_regs + CTL_LOW); + iowrite32(first->ctl_hi, lnwc->ch_regs + CTL_HIGH); + dma_dbg("TX SAR %lx, DAR %lx, CFGL %x, CFGH %x, CTLH %x, CTLL %x \n", + first->sar, first->dar, first->cfg_hi, + first->cfg_lo, first->ctl_hi, first->ctl_lo); + + iowrite32(ENABLE_CHANNEL(lnwc->ch_id), lnw->dma_base + DMA_CHAN_EN); + first->status = DMA_IN_PROGRESS; +} + +static void +lnwc_descriptor_complete1(struct lnw_dma_chan *lnwc, struct lnw_dma_desc *desc) +{ + struct dma_async_tx_descriptor *txd = &desc->txd; + dma_async_tx_callback callback = NULL; + dma_async_tx_callback callback_txd = NULL; + void *param = NULL; + void *param_txd = NULL; + u32 sar, dar, len; + union lnw_dma_ctl_hi ctl_hi; + + dma_dbg("called \n"); + + /*check if full tx is complete or not*/ + sar = ioread32(lnwc->ch_regs + SAR); + dar = ioread32(lnwc->ch_regs + DAR); + + if (desc->dirn == DMA_FROM_DEVICE) + len = dar - desc->dar; + else + len = sar - desc->sar; + + dma_dbg("SAR %x DAR %x, DMA done: %x \n", sar, dar, len); + if (desc->len > len) { + dma_dbg("dirn = %d\n", desc->dirn); + dma_dbg("SAR %x DAR %x, len: %x \n", sar, dar, len); + /*we have to copy more bytes*/ + desc->len -= len; + ctl_hi.ctl_hi = desc->ctl_hi; + ctl_hi.ctlx.block_ts = get_block_ts(desc->len, desc->width); + dma_dbg("setting for %x bytes \n", ctl_hi.ctlx.block_ts); + desc->ctl_hi = ctl_hi.ctl_hi; + if (desc->cfg_mode == LNW_DMA_MEM_TO_MEM) { + sar++; + dar++; + } else if (desc->dirn == DMA_TO_DEVICE) + sar++; + else if (desc->dirn == DMA_FROM_DEVICE) + dar++; + desc->sar = sar; + desc->dar = dar; + dma_dbg("New SAR %x DAR %x \n", sar, dar); + lnwc_dostart1(lnwc, desc); + return; + } + + lnwc->completed = txd->cookie; + callback = desc->callback; + param = desc->callback_param; + callback_txd = txd->callback; + param_txd = txd->callback_param; + + list_move(&desc->desc_node, &lnwc->free_list); + + spin_unlock_bh(&lnwc->lock); + dma_dbg("Now we are calling callback \n"); + if (callback_txd) { + dma_dbg("lnw TXD callback set ... calling \n"); + callback_txd(param_txd); + spin_lock_bh(&lnwc->lock); + return; + } + if (callback) { + dma_dbg("lnw callback set ... calling \n"); + callback(param); + } + spin_lock_bh(&lnwc->lock); +} + +/*check desc, mark as complete when tx is complete*/ +static void +lnwc_scan_descriptors1(struct lnwdma_device *lnw, struct lnw_dma_chan *lnwc) +{ + struct lnw_dma_desc *desc = NULL, *_desc = NULL; + u32 status_xfer; + + dma_dbg("called \n"); + status_xfer = ioread32(lnwc->dma_base + RAW_BLOCK); + status_xfer = (status_xfer >> lnwc->ch_id) & 0x1; + dma_dbg("ch[%d]: status_xfer %x \n", lnwc->ch_id, status_xfer); + if (!status_xfer) + return; + + list_for_each_entry_safe(desc, _desc, &lnwc->active_list, desc_node) { + if (desc == NULL) + continue; + if (desc->status == DMA_IN_PROGRESS) { + desc->status = DMA_SUCCESS; + lnwc_descriptor_complete1(lnwc, desc); + } + } + return; +} + +/***************************************************************************** +DMA Functions*/ +static dma_cookie_t lnw_dma1_tx_submit(struct dma_async_tx_descriptor *tx) +{ + struct lnw_dma_desc *desc = to_lnw_dma_desc(tx); + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(tx->chan); + dma_cookie_t cookie; + + dma_dbg("called \n"); + spin_lock_bh(&lnwc->lock); + cookie = lnwc->chan.cookie; + + if (++cookie < 0) + cookie = 1; + + lnwc->chan.cookie = cookie; + desc->txd.cookie = cookie; + + + if (list_empty(&lnwc->active_list)) { + lnwc_dostart1(lnwc, desc); + list_add_tail(&desc->desc_node, &lnwc->active_list); + } else { + list_add_tail(&desc->desc_node, &lnwc->queue); + } + spin_unlock_bh(&lnwc->lock); + + return cookie; +} + +static void lnw_dma1_issue_pending(struct dma_chan *chan) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + + spin_lock_bh(&lnwc->lock); + if (!list_empty(&lnwc->queue)) + lnwc_scan_descriptors1(to_lnwdma_device(chan->device), lnwc); + spin_unlock_bh(&lnwc->lock); +} + +static enum dma_status +lnw_dma1_tx_is_complete(struct dma_chan *chan, + dma_cookie_t cookie, + dma_cookie_t *done, + dma_cookie_t *used) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + dma_cookie_t last_used; + dma_cookie_t last_complete; + int ret; + + last_complete = lnwc->completed; + last_used = chan->cookie; + + ret = dma_async_is_complete(cookie, last_complete, last_used); + if (ret != DMA_SUCCESS) { + lnwc_scan_descriptors1(to_lnwdma_device(chan->device), lnwc); + + last_complete = lnwc->completed; + last_used = chan->cookie; + + ret = dma_async_is_complete(cookie, last_complete, last_used); + } + + if (done) + *done = last_complete; + if (used) + *used = last_used; + + return ret; +} + +static void lnw_dma1_terminate_all(struct dma_chan *chan) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + struct lnwdma_device *lnw = to_lnwdma_device(chan->device); + struct lnw_dma_desc *desc, *_desc; + LIST_HEAD(list); + + /* ASSERT: channel is idle */ + if (lnwc->in_use == false) { + /*ch is not in use, wrong call*/ + return; + } + spin_lock_bh(&lnwc->lock); + list_splice_init(&lnwc->free_list, &list); + lnwc->descs_allocated = 0; + lnwc->slave = NULL; + + /* Disable interrupts */ + disable_dma1_interrupt(lnwc); + + spin_unlock_bh(&lnwc->lock); + list_for_each_entry_safe(desc, _desc, &list, desc_node) { + dma_dbg("freeing descriptor %p\n", desc); + pci_pool_free(lnw->dma_pool, desc, desc->txd.phys); + } + return; +} + +static struct dma_async_tx_descriptor * +lnw_dma1_prep_slave_sg(struct dma_chan *chan, + struct scatterlist *sgl, unsigned int sg_len, + enum dma_data_direction direction, + unsigned long flags) +{ + /*not supported now*/ + return NULL; +} + +static struct dma_async_tx_descriptor * +lnw_dma1_prep_memcpy(struct dma_chan *chan, dma_addr_t dest, + dma_addr_t src, size_t len, unsigned long flags) +{ + struct lnw_dma_chan *lnwc; + struct lnw_dma_desc *desc = NULL; + struct lnw_dma_slave *lnws; + union lnw_dma_ctl_lo ctl_lo; + union lnw_dma_ctl_hi ctl_hi; + union lnw_dma_cfg_lo cfg_lo; + union lnw_dma_cfg_hi cfg_hi; + enum lnw_dma_width width = 0; + + dma_dbg("called \n"); + WARN_ON(!chan); + if (!len) + return NULL; + + lnws = chan->private; + WARN_ON(!lnws); + + lnwc = to_lnw_dma_chan(chan); + WARN_ON(!lnwc); + + dma_dbg("called for CH %d\n", lnwc->ch_id); + dma_dbg("Cfg passed Mode %x, Dirn %x, HS %x, Width %x \n", + lnws->cfg_mode, lnws->dirn, lnws->hs_mode, lnws->src_width); + + /*calculate CFG_LO*/ + if (lnws->hs_mode == LNW_DMA_SW_HS) { + cfg_lo.cfg_lo = 0; + cfg_lo.cfgx.hs_sel_dst = 1; + cfg_lo.cfgx.hs_sel_src = 1; + } else if (lnws->hs_mode == LNW_DMA_HW_HS) + cfg_lo.cfg_lo = 0x00000; + + /*calculate CFG_HI*/ + if (lnws->cfg_mode == LNW_DMA_MEM_TO_MEM) { + /*SW HS only*/ + dma_dbg("CFG: Mem to mem dma \n"); + cfg_hi.cfg_hi = 0; + } else { + dma_dbg("HW DMA \n"); + cfg_hi.cfg_hi = 0; + cfg_hi.cfgx.protctl = 0x0; /*default value*/ + cfg_hi.cfgx.fifo_mode = 1; + if (lnws->dirn == DMA_TO_DEVICE) { + cfg_hi.cfgx.src_per = 0; + cfg_hi.cfgx.dst_per = 3; + } else if (lnws->dirn == DMA_FROM_DEVICE) { + cfg_hi.cfgx.src_per = 2; + cfg_hi.cfgx.dst_per = 0; + } + } + + /*calculate CTL_HI*/ + ctl_hi.ctlx.reser = 0; + width = lnws->src_width; + + ctl_hi.ctlx.block_ts = get_block_ts(len, width); + + /*calculate CTL_LO*/ + ctl_lo.ctl_lo = 0; + ctl_lo.ctlx.int_en = 1; + ctl_lo.ctlx.dst_tr_width = lnws->dst_width; + ctl_lo.ctlx.src_tr_width = lnws->src_width; + ctl_lo.ctlx.dst_msize = lnws->src_msize; + ctl_lo.ctlx.src_msize = lnws->dst_msize; + + if (lnws->cfg_mode == LNW_DMA_MEM_TO_MEM) { + dma_dbg("CTL: Mem to mem dma \n"); + ctl_lo.ctlx.tt_fc = 0; + ctl_lo.ctlx.sinc = 0; + ctl_lo.ctlx.dinc = 0; + } else { + if (lnws->dirn == DMA_TO_DEVICE) { + dma_dbg("CTL: DMA_TO_DEVICE \n"); + ctl_lo.ctlx.sinc = 0; + ctl_lo.ctlx.dinc = 2; + ctl_lo.ctlx.tt_fc = 1; + } else if (lnws->dirn == DMA_FROM_DEVICE) { + dma_dbg("CTL: DMA_FROM_DEVICE \n"); + ctl_lo.ctlx.sinc = 2; + ctl_lo.ctlx.dinc = 0; + ctl_lo.ctlx.tt_fc = 2; + } + } + + dma_dbg("Calc CTL LO %x, CTL HI %x, CFG LO %x, CFG HI %x\n", + ctl_lo.ctl_lo, ctl_hi.ctl_hi, cfg_lo.cfg_lo, cfg_hi.cfg_hi); + + enable_dma1_interrupt(lnwc); + + desc = lnwc_desc_get1(lnwc); + if (desc == NULL) + goto err_desc_get; + desc->sar = src; + desc->dar = dest ; + desc->len = len; + desc->cfg_hi = cfg_hi.cfg_hi; + desc->cfg_lo = cfg_lo.cfg_lo; + desc->ctl_lo = ctl_lo.ctl_lo; + desc->ctl_hi = ctl_hi.ctl_hi; + desc->width = width; + desc->dirn = lnws->dirn; + if (lnws->callback) { + desc->callback = lnws->callback; + desc->callback_param = lnws->callback_param; + dma_dbg("Callback passed... setting\n"); + } else + desc->callback = NULL; + return &desc->txd; + +err_desc_get: + dma_err("Failed to get desc \n"); + lnwc_desc_put1(lnwc, desc); + return NULL; +} + +static void lnw_dma1_free_chan_resources(struct dma_chan *chan) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + struct lnwdma_device *lnw = to_lnwdma_device(chan->device); + struct lnw_dma_desc *desc, *_desc; + + dma_dbg("..called for ch_id %d, lnwch_id %d\n", + chan->chan_id, lnwc->ch_id); + if (true == lnwc->in_use) { + /*trying to free ch in use!!!!!*/ + dma_err("trying to free ch in use \n"); + } + + spin_lock_bh(&lnwc->lock); + lnwc->descs_allocated = 0; + list_for_each_entry_safe(desc, _desc, &lnwc->active_list, desc_node) { + dma_dbg("del active \n"); + list_del(&desc->desc_node); + pci_pool_free(lnw->dma_pool, desc, desc->txd.phys); + } + list_for_each_entry_safe(desc, _desc, &lnwc->free_list, desc_node) { + list_del(&desc->desc_node); + pci_pool_free(lnw->dma_pool, desc, desc->txd.phys); + } + list_for_each_entry_safe(desc, _desc, &lnwc->queue, desc_node) { + dma_dbg("del queue \n"); + list_del(&desc->desc_node); + pci_pool_free(lnw->dma_pool, desc, desc->txd.phys); + } + spin_unlock_bh(&lnwc->lock); + lnwc->in_use = false; + chan->client_count--; + /* Disable CH interrupts */ + iowrite32(MASK_INTR_REG(lnwc->ch_id), lnw->dma_base + MASK_BLOCK); + iowrite32(MASK_INTR_REG(lnwc->ch_id), lnw->dma_base + MASK_ERR); + dma_dbg("done \n"); +} + +static int lnw_dma1_alloc_chan_resources(struct dma_chan *chan) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + struct lnwdma_device *lnw = to_lnwdma_device(chan->device); + struct lnw_dma_desc *desc; + dma_addr_t phys; + int i = 0; + + dma_dbg("called \n"); + + /* ASSERT: channel is idle */ + if (test_ch_en(lnw->dma_base, lnwc->ch_id)) { + /*ch is not idle*/ + dma_err(".ch not idle\n"); + return -EIO; + } + dma_dbg("..called for ch_id %d, lnwch_id %d\n", + chan->chan_id, lnwc->ch_id); + lnwc->completed = chan->cookie = 1; + + chan->client_count++; + + spin_lock_bh(&lnwc->lock); + while (lnwc->descs_allocated < DESCS_PER_CHANNEL) { + spin_unlock_bh(&lnwc->lock); + desc = pci_pool_alloc(lnw->dma_pool, GFP_KERNEL, &phys); + if (!desc) { + dma_err("desc failed\n"); + return -ENOMEM; + /*check*/ + } + dma_async_tx_descriptor_init(&desc->txd, chan); + desc->txd.tx_submit = lnw_dma1_tx_submit; + desc->txd.flags = DMA_CTRL_ACK; + desc->txd.phys = phys; + spin_lock_bh(&lnwc->lock); + i = ++lnwc->descs_allocated; + list_add_tail(&desc->desc_node, &lnwc->free_list); + } + spin_unlock_bh(&lnwc->lock); + lnwc->in_use = false; + dma_dbg("Desc alloc done ret: %d desc\n", i); + return i; +} + +static void lnwc_handle_error1(struct lnwdma_device *lnw, + struct lnw_dma_chan *lnwc) +{ + lnwc_scan_descriptors1(lnw, lnwc); +} + +/****************************************************************************** +* PCI stuff +*/ +static struct pci_device_id lnw_dma1_ids[] = { + { PCI_VENDOR_ID_INTEL, 0x0814, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, lnw_dma1_ids); + +static struct pci_driver lnw_dma1_pci = { + .name = "Intel LNW DMA1", + .id_table = lnw_dma1_ids, + .probe = lnw_dma1_probe, + .remove = __devexit_p(lnw_dma1_remove), +}; + +static void dma_tasklet1(unsigned long data) +{ + struct lnwdma_device *lnw = NULL; + struct lnw_dma_chan *lnwc = NULL; + u32 status; + int i, ch_no; + + dma_dbg("called \n"); + lnw = (struct lnwdma_device *)data; + if (lnw == NULL) { + dma_err("Null param \n"); + return; + } + status = ioread32(lnw->dma_base + RAW_BLOCK); + dma_dbg("RAW_TFR %x \n", status); + status &= 0xC0; + while (status) { + /*txn interrupt*/ + ch_no = get_ch_num(&status); + if (ch_no < 0) { + dma_err("Ch no is invalid %x, abort!\n", ch_no); + return; + } + dma_dbg("Got Ch %x, new Status %x \n", ch_no, status); + i = get_ch_index(ch_no); + if (i < 0) { + dma_err("Invalid ch index %x\n", i); + return; + } + dma_dbg("Tx complete interrupt %x, Ch No %d Index %d \n", + status, ch_no, i); + lnwc = &lnw->ch[i]; + if (lnwc == NULL) { + dma_err("Null param lnwc\n"); + return; + } + dma_dbg("CH %x \n", lnwc->ch_id); + spin_lock_bh(&lnwc->lock); + lnwc_scan_descriptors1(lnw, lnwc); + dma_dbg("Scan of desc... complete, unmasking\n"); + iowrite32((1 << lnwc->ch_id), + lnw->dma_base + CLEAR_TFR); + dma_dbg("Wrote to clear %x\n", (1 << lnwc->ch_id)); + iowrite32((1 << lnwc->ch_id), + lnw->dma_base + CLEAR_BLOCK); + iowrite32(UNMASK_INTR_REG(lnwc->ch_id), + lnw->dma_base + MASK_TFR); + spin_unlock_bh(&lnwc->lock); + } + + dma_dbg("Trf interrupt done... \n"); + status = ioread32(lnw->dma_base + RAW_ERR); + status &= 0xC0; + while (status) { + /*err interrupt*/ + ch_no = get_ch_num(&status); + if (ch_no < 0) { + dma_err("Ch no is invalid %x, abort!\n", ch_no); + return; + } + dma_dbg("Got Ch %x, new Status %x \n", ch_no, status); + i = get_ch_index(ch_no); + if (i < 0) { + dma_err("Invalid CH lnwc\n"); + return; + } + dma_dbg("Tx error interrupt %x, No %d Index %d \n", + status, ch_no, i); + lnwc = &lnw->ch[i]; + if (lnwc == NULL) { + dma_err("Null param lnwc\n"); + return; + } + spin_lock_bh(&lnwc->lock); + lnwc_handle_error1(lnw, lnwc); + iowrite32((1 << lnwc->ch_id), + lnw->dma_base + CLEAR_ERR); + iowrite32(UNMASK_INTR_REG(lnwc->ch_id), + lnw->dma_base + MASK_ERR); + spin_unlock_bh(&lnwc->lock); + } + dma_dbg("Exiting takslet... \n"); + return; +} + +static irqreturn_t lnw_dma1_interrupt(int irq, void *data) +{ + struct lnw_device *lnw = data; + u32 status; + int call_tasklet = 0; + + /*check interrupt src*/ + lpe_periphral_intr_status(LPE_DMA, &status); + if (!status) { + /*not our interrupt*/ + return IRQ_NONE; + } + + /*DMA Interrupt*/ + status = ioread32(lnw->dma_base + RAW_TFR); + status &= 0xC0; + if (status) { + iowrite32((status << 8), lnw->dma_base + MASK_TFR); + call_tasklet = 1; + } + status = ioread32(lnw->dma_base + RAW_ERR); + status &= 0xC0; + if (status) { + iowrite32(MASK_INTR_REG(status), lnw->dma_base + MASK_ERR); + call_tasklet = 1; + } + + if (call_tasklet) + tasklet_schedule(&lnw->dma->tasklet); + + return IRQ_HANDLED; +} + +static void enable_dma1_interrupt(struct lnw_dma_chan *lnwc) +{ + dma_dbg("Called for ch_id %d\n", lnwc->ch_id); + + lpe_unmask_periphral_intr(LPE_DMA); + + /*en ch interrupts*/ + iowrite32(UNMASK_INTR_REG(lnwc->ch_id), lnwc->dma_base + MASK_TFR); + iowrite32(UNMASK_INTR_REG(lnwc->ch_id), lnwc->dma_base + MASK_ERR); + return; +} + +static void disable_dma1_interrupt(struct lnw_dma_chan *lnwc) +{ + /*Check LPE PISR, make sure fwd is disabled*/ + lpe_mask_periphral_intr(LPE_DMA); + iowrite32(MASK_INTR_REG(lnwc->ch_id), lnwc->dma_base + MASK_BLOCK); + iowrite32(MASK_INTR_REG(lnwc->ch_id), lnwc->dma_base + MASK_TFR); + iowrite32(MASK_INTR_REG(lnwc->ch_id), lnwc->dma_base + MASK_ERR); + dma_dbg(" called \n"); + return; +} + +static int lnw_setup_dma1(struct pci_dev *pdev) +{ + struct lnw_device *device = pci_get_drvdata(pdev); + struct lnwdma_device *dma = NULL; + int err, i; + + dma_dbg("setup_dma called \n"); + dma = kzalloc(sizeof(*dma), GFP_KERNEL); + if (NULL == dma) { + dma_err("kzalloc failed \n"); + err = -ENOMEM; + goto err_kzalloc; + } + device->dma = dma; + dma->pdev = pdev; + dma->dma_base = device->dma_base; + + /* DMA coherent memory pool for DMA descriptor allocations */ + dma->dma_pool = pci_pool_create("dma_desc_pool", pdev, + sizeof(struct lnw_dma_desc), + 32, 0); + if (NULL == dma->dma_pool) { + dma_err("pci_pool_create failed \n"); + err = -ENOMEM; + kfree(dma); + goto err_dma_pool; + } + + INIT_LIST_HEAD(&dma->common.channels); + + + /*init CH structures*/ + for (i = 0; i < MAX_CHAN; i++) { + struct lnw_dma_chan *lnwch = &dma->ch[i]; + + lnwch->chan.device = &dma->common; + lnwch->chan.cookie = 1; + lnwch->chan.chan_id = i; + lnwch->ch_id = get_ch_id(i); + dma_dbg("Init CH %d, ID %d \n", i, lnwch->ch_id); + + lnwch->dma_base = dma->dma_base; + lnwch->ch_regs = dma->dma_base + DMA_CH_SIZE * lnwch->ch_id; + lnwch->dma = dma; + spin_lock_init(&lnwch->lock); + + INIT_LIST_HEAD(&lnwch->active_list); + INIT_LIST_HEAD(&lnwch->queue); + INIT_LIST_HEAD(&lnwch->free_list); + /*mask interrupts*/ + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_BLOCK); + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_SRC_TRAN); + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_DST_TRAN); + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_ERR); + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_TFR); + + disable_dma1_interrupt(lnwch); + list_add_tail(&lnwch->chan.device_node, &dma->common.channels); + } + + /*init dma structure*/ + dma_cap_zero(dma->common.cap_mask); + dma_cap_set(DMA_MEMCPY, dma->common.cap_mask); + dma_cap_set(DMA_SLAVE, dma->common.cap_mask); + dma_cap_set(DMA_PRIVATE, dma->common.cap_mask); + dma->common.dev = &pdev->dev; + dma->common.chancnt = MAX_CHAN; + + dma->common.device_alloc_chan_resources = + lnw_dma1_alloc_chan_resources; + dma->common.device_free_chan_resources = + lnw_dma1_free_chan_resources; + + dma->common.device_is_tx_complete = lnw_dma1_tx_is_complete; + dma->common.device_prep_dma_memcpy = lnw_dma1_prep_memcpy; + dma->common.device_issue_pending = lnw_dma1_issue_pending; + dma->common.device_prep_slave_sg = lnw_dma1_prep_slave_sg; + dma->common.device_terminate_all = lnw_dma1_terminate_all; + + /*enable dma cntrl*/ + iowrite32(REG_BIT0, dma->dma_base + DMA_CFG); + + /*register irq*/ + err = request_irq(pdev->irq, lnw_dma1_interrupt, + IRQF_SHARED, lnw_dma1_pci.name, device); + if (0 != err) + goto err_irq; + + /*register device w/ engine*/ + err = dma_async_device_register(&dma->common); + if (0 != err) { + dma_err("device_register failed: %d \n", err); + goto err_engine; + } + tasklet_init(&dma->tasklet, dma_tasklet1, (unsigned long)dma); + dma_dbg("...done \n"); + return 0; + +err_engine: + free_irq(pdev->irq, device); +err_irq: + pci_pool_destroy(dma->dma_pool); + kfree(dma); +err_dma_pool: +err_kzalloc: + dma_err("setup_dma failed: %d \n", err); + return err; + +} + +static void lnwdma_shutdown1(struct pci_dev *pdev) +{ + struct lnw_device *device = pci_get_drvdata(pdev); + + dma_dbg("shutdown called \n"); + dma_async_device_unregister(&device->dma->common); + pci_pool_destroy(device->dma->dma_pool); + if (device->dma_base) + iounmap(device->dma_base); + free_irq(pdev->irq, device); + return; +} + +static int __devinit +lnw_dma1_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct lnw_device *device = NULL; + u32 base_addr = 0, bar_size = 0; + int err = 0; + + dma_info("probe called for %x \n", pdev->device); + err = pci_enable_device(pdev); + if (err) + goto err_enable_device; + + err = pci_request_regions(pdev, lnw_dma1_pci.name); + if (err) + goto err_request_regions; + + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (err) + goto err_set_dma_mask; + + err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + if (err) + goto err_set_dma_mask; + + device = kzalloc(sizeof(*device), GFP_KERNEL); + if (!device) { + dma_err("kzalloc failed \n"); + err = -ENOMEM; + goto err_kzalloc; + } + device->pdev = pci_dev_get(pdev); + + base_addr = pci_resource_start(pdev, 0); + bar_size = pci_resource_len(pdev, 0); + dma_dbg("BAR0 %x Size %x \n", base_addr, bar_size); + device->dma_base = ioremap_nocache(base_addr, DMA_REG_SIZE); + if (!device->dma_base) { + dma_err("ioremap failed \n"); + err = -ENOMEM; + goto err_ioremap1; + } + pci_set_drvdata(pdev, device); + pci_set_master(pdev); + + err = lnw_setup_dma1(pdev); + if (err) + goto err_dma; + + return 0; + +err_dma: + iounmap(device->dma_base); +err_ioremap1: + pci_dev_put(pdev); + kfree(device); +err_kzalloc: +err_set_dma_mask: + pci_release_regions(pdev); + pci_disable_device(pdev); +err_request_regions: +err_enable_device: + dma_err("Probe failed %d\n", err); + return err; +} + +static void __devexit lnw_dma1_remove(struct pci_dev *pdev) +{ + struct lnw_device *device = pci_get_drvdata(pdev); + + lnwdma_shutdown1(pdev); + pci_dev_put(pdev); + kfree(device); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static int __init lnw_dma1_init(void) +{ + dma_info("LNW DMA Driver\n Version %s \n", LNW_DMA_DRIVER_VERSION); + return pci_register_driver(&lnw_dma1_pci); +} +late_initcall(lnw_dma1_init); + +static void __exit lnw_dma1_exit(void) +{ + pci_unregister_driver(&lnw_dma1_pci); +} +module_exit(lnw_dma1_exit); + Index: linux-2.6.33/drivers/dma/lnw_dmac2.c =================================================================== --- /dev/null +++ linux-2.6.33/drivers/dma/lnw_dmac2.c @@ -0,0 +1,947 @@ +/* + * lnw_dmac2.c - Intel Langwell DMA Drivers + * + * Copyright (C) 2008-09 Intel Corp + * Author: Vinod Koul + * The driver design is based on dw_dmac driver + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#include +#include +#include +#include +#include + +#define MAX_CHAN 2 +#include "lnw_dma_regs.h" + +MODULE_AUTHOR("Vinod Koul "); +MODULE_DESCRIPTION("Intel (R) Moorestown Langwell DMAC2 Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(LNW_DMA_DRIVER_VERSION); + +#define DMA_CH0 0 +#define DMA_CH1 1 +#define CH_BLOCK_SIZE 2047 + +static int __devinit lnw_dma2_probe(struct pci_dev *pdev, + const struct pci_device_id *id); +static void __devexit lnw_dma2_remove(struct pci_dev *pdev); +static void enable_dma2_interrupt(struct lnw_dma_chan *lnwc); + +struct lnw_device { + struct pci_dev *pdev; + void __iomem *dma_base; + struct lnwdma_device *dma; +}; + +/*CH dep code, if ch no's mapping changes only change here*/ +static int get_ch_id(int index) +{ + if (index == 0) + return DMA_CH0; + else if (index == 1) + return DMA_CH1; + else + return -1; +} + +static int get_ch_index(int ch_id) +{ + if (ch_id == DMA_CH0) + return 0; + if (ch_id == DMA_CH1) + return 1; + else + return -1; +} + +static int get_ch_num(int *status) +{ + if (*status & (1 << DMA_CH0)) { + *status = *status & (~(1 << DMA_CH0)); + return DMA_CH0; + } else if (*status & (1 << DMA_CH1)) { + *status = *status & (~(1 << DMA_CH1)); + return DMA_CH1; + } else + return -1; +} + +static int get_block_ts(int len, int tx_width) +{ + int byte_width = 0, block_ts = 0; + + switch (tx_width) { + case LNW_DMA_WIDTH_8BIT: + byte_width = 1; + break; + case LNW_DMA_WIDTH_16BIT: + byte_width = 2; + break; + case LNW_DMA_WIDTH_32BIT: + default: + byte_width = 4; + break; + } + + block_ts = len/byte_width; + if (block_ts > CH_BLOCK_SIZE) + block_ts = 0xFFFF; + return block_ts; +} + +static struct lnw_dma_desc *lnwc_desc_get(struct lnw_dma_chan *lnwc) +{ + struct lnw_dma_desc *desc, *_desc; + struct lnw_dma_desc *ret = NULL; + + dma_dbg("called \n"); + spin_lock_bh(&lnwc->lock); + list_for_each_entry_safe(desc, _desc, &lnwc->free_list, desc_node) { + if (async_tx_test_ack(&desc->txd)) { + list_del(&desc->desc_node); + ret = desc; + dma_dbg("got free desc \n"); + break; + } + } + spin_unlock_bh(&lnwc->lock); + return ret; +} + +static void lnwc_desc_put(struct lnw_dma_chan *lnwc, struct lnw_dma_desc *desc) +{ + if (desc) { + spin_lock_bh(&lnwc->lock); + list_add_tail(&desc->desc_node, &lnwc->free_list); + spin_unlock_bh(&lnwc->lock); + } +} + +/* Called with lock held and bh disabled */ +static void lnwc_dostart(struct lnw_dma_chan *lnwc, struct lnw_dma_desc *first) +{ + struct lnwdma_device *lnw = to_lnwdma_device(lnwc->chan.device); + + dma_dbg("called \n"); + /* channel is idle */ + if (lnwc->in_use && test_ch_en(lnwc->dma_base, lnwc->ch_id)) { + /*error*/ + dma_err("channel is busy \n"); + /* The tasklet will hopefully advance the queue... */ + return; + } + + /*write registers and en*/ + iowrite32(first->sar, lnwc->ch_regs + SAR); + iowrite32(first->dar, lnwc->ch_regs + DAR); + iowrite32(first->cfg_hi, lnwc->ch_regs + CFG_HIGH); + iowrite32(first->cfg_lo, lnwc->ch_regs + CFG_LOW); + iowrite32(first->ctl_lo, lnwc->ch_regs + CTL_LOW); + iowrite32(first->ctl_hi, lnwc->ch_regs + CTL_HIGH); + dma_dbg("TX SAR %lx, DAR %lx, CFGL %x, CFGH %x, CTLH %x, CTLL %x \n", + first->sar, first->dar, first->cfg_hi, + first->cfg_lo, first->ctl_hi, first->ctl_lo); + + iowrite32(ENABLE_CHANNEL(lnwc->ch_id), lnw->dma_base + DMA_CHAN_EN); + first->status = DMA_IN_PROGRESS; +} + +static void +lnwc_descriptor_complete(struct lnw_dma_chan *lnwc, struct lnw_dma_desc *desc) +{ + struct dma_async_tx_descriptor *txd = &desc->txd; + dma_async_tx_callback callback = NULL; + dma_async_tx_callback callback_txd = NULL; + void *param = NULL; + void *param_txd = NULL; + u32 sar, dar, len; + union lnw_dma_ctl_hi ctl_hi; + + dma_dbg("called \n"); + + /*check if full tx is complete or not*/ + sar = ioread32(lnwc->ch_regs + SAR); + dar = ioread32(lnwc->ch_regs + DAR); + + if (desc->dirn == DMA_FROM_DEVICE) + len = dar - desc->dar; + else + len = sar - desc->sar; + + dma_dbg("SAR %x DAR %x, DMA done: %x \n", sar, dar, len); + if (desc->len > len) { + dma_dbg("dirn = %d\n", desc->dirn); + dma_dbg("SAR %x DAR %x, len: %x \n", sar, dar, len); + /*we have to copy more bytes*/ + desc->len -= len; + ctl_hi.ctl_hi = desc->ctl_hi; + ctl_hi.ctlx.block_ts = get_block_ts(desc->len, desc->width); + dma_dbg("setting for %x bytes \n", ctl_hi.ctlx.block_ts); + desc->ctl_hi = ctl_hi.ctl_hi; + if (desc->cfg_mode == LNW_DMA_MEM_TO_MEM) { + sar++; + dar++; + } else if (desc->dirn == DMA_TO_DEVICE) + sar++; + else if (desc->dirn == DMA_FROM_DEVICE) + dar++; + desc->sar = sar; + desc->dar = dar; + dma_dbg("New SAR %x DAR %x \n", sar, dar); + lnwc_dostart(lnwc, desc); + return; + } + + lnwc->completed = txd->cookie; + callback = desc->callback; + param = desc->callback_param; + callback_txd = txd->callback; + param_txd = txd->callback_param; + + list_move(&desc->desc_node, &lnwc->free_list); + + spin_unlock_bh(&lnwc->lock); + dma_dbg("Now we are calling callback \n"); + if (callback_txd) { + dma_dbg("lnw TXD callback set ... calling \n"); + callback_txd(param_txd); + spin_lock_bh(&lnwc->lock); + return; + } + if (callback) { + dma_dbg("lnw callback set ... calling \n"); + callback(param); + } + spin_lock_bh(&lnwc->lock); + +} + +/*check desc, mark as complete when tx is complete*/ +static void +lnwc_scan_descriptors(struct lnwdma_device *lnw, struct lnw_dma_chan *lnwc) +{ + struct lnw_dma_desc *desc = NULL, *_desc = NULL; + u32 status_xfer; + + dma_dbg("called \n"); + status_xfer = ioread32(lnwc->dma_base + RAW_TFR); + status_xfer = (status_xfer >> lnwc->ch_id) & 0x1; + dma_dbg("ch[%d]: status_xfer %x \n", lnwc->ch_id, status_xfer); + if (!status_xfer) + return; + + /*tx is complete*/ + list_for_each_entry_safe(desc, _desc, &lnwc->active_list, desc_node) { + if (desc == NULL) + continue; + if (desc->status == DMA_IN_PROGRESS) { + desc->status = DMA_SUCCESS; + lnwc_descriptor_complete(lnwc, desc); + } + } + return; +} + +/***************************************************************************** +DMA Functions*/ +static dma_cookie_t lnw_dma2_tx_submit(struct dma_async_tx_descriptor *tx) +{ + struct lnw_dma_desc *desc = to_lnw_dma_desc(tx); + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(tx->chan); + dma_cookie_t cookie; + + dma_dbg("called \n"); + + spin_lock_bh(&lnwc->lock); + cookie = lnwc->chan.cookie; + + if (++cookie < 0) + cookie = 1; + + lnwc->chan.cookie = cookie; + desc->txd.cookie = cookie; + + if (list_empty(&lnwc->active_list)) { + lnwc_dostart(lnwc, desc); + list_add_tail(&desc->desc_node, &lnwc->active_list); + } else { + list_add_tail(&desc->desc_node, &lnwc->queue); + } + spin_unlock_bh(&lnwc->lock); + + return cookie; +} + +static void lnw_dma2_issue_pending(struct dma_chan *chan) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + + spin_lock_bh(&lnwc->lock); + if (!list_empty(&lnwc->queue)) + lnwc_scan_descriptors(to_lnwdma_device(chan->device), lnwc); + spin_unlock_bh(&lnwc->lock); +} + +static enum dma_status +lnw_dma2_tx_is_complete(struct dma_chan *chan, + dma_cookie_t cookie, + dma_cookie_t *done, + dma_cookie_t *used) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + dma_cookie_t last_used; + dma_cookie_t last_complete; + int ret; + + last_complete = lnwc->completed; + last_used = chan->cookie; + + ret = dma_async_is_complete(cookie, last_complete, last_used); + if (ret != DMA_SUCCESS) { + lnwc_scan_descriptors(to_lnwdma_device(chan->device), lnwc); + + last_complete = lnwc->completed; + last_used = chan->cookie; + + ret = dma_async_is_complete(cookie, last_complete, last_used); + } + + if (done) + *done = last_complete; + if (used) + *used = last_used; + + return ret; +} + +static void lnw_dma2_terminate_all(struct dma_chan *chan) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + struct lnwdma_device *lnw = to_lnwdma_device(chan->device); + struct lnw_dma_desc *desc, *_desc; + LIST_HEAD(list); + + /* ASSERT: channel is idle */ + if (lnwc->in_use == false) { + /*ch is not in use, wrong call*/ + return; + } + spin_lock_bh(&lnwc->lock); + list_splice_init(&lnwc->free_list, &list); + lnwc->descs_allocated = 0; + lnwc->slave = NULL; + + /* Disable interrupts*/ + iowrite32(MASK_INTR_REG(lnwc->ch_id), lnw->dma_base + MASK_BLOCK); + iowrite32(MASK_INTR_REG(lnwc->ch_id), lnw->dma_base + MASK_ERR); + + spin_unlock_bh(&lnwc->lock); + list_for_each_entry_safe(desc, _desc, &list, desc_node) { + dma_dbg("freeing descriptor %p\n", desc); + pci_pool_free(lnw->dma_pool, desc, desc->txd.phys); + } + + return; +} + +static struct dma_async_tx_descriptor * +lnw_dma2_prep_slave_sg(struct dma_chan *chan, + struct scatterlist *sgl, unsigned int sg_len, + enum dma_data_direction direction, + unsigned long flags) +{ + /*not supported now*/ + return NULL; +} + +static struct dma_async_tx_descriptor * +lnw_dma2_prep_memcpy(struct dma_chan *chan, dma_addr_t dest, + dma_addr_t src, size_t len, unsigned long flags) +{ + struct lnw_dma_chan *lnwc; + struct lnw_dma_desc *desc = NULL; + struct lnw_dma_slave *lnws; + union lnw_dma_ctl_lo ctl_lo; + union lnw_dma_ctl_hi ctl_hi; + union lnw_dma_cfg_lo cfg_lo; + union lnw_dma_cfg_hi cfg_hi; + enum lnw_dma_width width = 0; + + dma_dbg("called \n"); + WARN_ON(!chan); + if (!len) + return NULL; + + lnws = chan->private; + WARN_ON(!lnws); + + lnwc = to_lnw_dma_chan(chan); + WARN_ON(!lnwc); + + dma_dbg("called for CH %d\n", lnwc->ch_id); + dma_dbg("Cfg passed Mode %x, Dirn %x, HS %x, Width %x \n", + lnws->cfg_mode, lnws->dirn, lnws->hs_mode, lnws->src_width); + + /*calculate CFG_LO*/ + if (lnws->hs_mode == LNW_DMA_SW_HS) { + cfg_lo.cfg_lo = 0; + cfg_lo.cfgx.hs_sel_dst = 1; + cfg_lo.cfgx.hs_sel_src = 1; + } else if (lnws->hs_mode == LNW_DMA_HW_HS) + cfg_lo.cfg_lo = 0x00000; + + /*calculate CFG_HI*/ + if (lnws->cfg_mode == LNW_DMA_MEM_TO_MEM) { + /*SW HS only*/ + dma_dbg("CFG: Mem to mem dma \n"); + cfg_hi.cfg_hi = 0; + } else { + dma_dbg("HW DMA \n"); + cfg_hi.cfg_hi = 0; + cfg_hi.cfgx.protctl = 0x1; /*default value*/ + cfg_hi.cfgx.src_per = get_ch_index(lnwc->ch_id); + cfg_hi.cfgx.dst_per = get_ch_index(lnwc->ch_id); + } + + /*calculate CTL_HI*/ + ctl_hi.ctlx.reser = 0; + width = lnws->src_width; + ctl_hi.ctlx.block_ts = get_block_ts(len, width); + + /*calculate CTL_LO*/ + ctl_lo.ctl_lo = 0; + ctl_lo.ctlx.int_en = 1; + ctl_lo.ctlx.dst_tr_width = lnws->dst_width; + ctl_lo.ctlx.src_tr_width = lnws->src_width; + ctl_lo.ctlx.dst_msize = lnws->src_msize; + ctl_lo.ctlx.src_msize = lnws->dst_msize; + + if (lnws->cfg_mode == LNW_DMA_MEM_TO_MEM) { + dma_dbg("CTL: Mem to mem dma \n"); + ctl_lo.ctlx.tt_fc = 0; + ctl_lo.ctlx.sinc = 0; + ctl_lo.ctlx.dinc = 0; + } else { + if (lnws->dirn == DMA_TO_DEVICE) { + dma_dbg("CTL: DMA_TO_DEVICE \n"); + ctl_lo.ctlx.sinc = 0; + ctl_lo.ctlx.dinc = 2; + ctl_lo.ctlx.tt_fc = 1; + } else if (lnws->dirn == DMA_FROM_DEVICE) { + dma_dbg("CTL: DMA_FROM_DEVICE \n"); + ctl_lo.ctlx.sinc = 2; + ctl_lo.ctlx.dinc = 0; + ctl_lo.ctlx.tt_fc = 2; + } + } + + dma_dbg("Calc CTL LO %x, CTL HI %x, CFG LO %x, CFG HI %x\n", + ctl_lo.ctl_lo, ctl_hi.ctl_hi, cfg_lo.cfg_lo, cfg_hi.cfg_hi); + + enable_dma2_interrupt(lnwc); + + desc = lnwc_desc_get(lnwc); + if (desc == NULL) + goto err_desc_get; + desc->sar = src; + desc->dar = dest ; + desc->len = len; + desc->cfg_hi = cfg_hi.cfg_hi; + desc->cfg_lo = cfg_lo.cfg_lo; + desc->ctl_lo = ctl_lo.ctl_lo; + desc->ctl_hi = ctl_hi.ctl_hi; + desc->width = width; + desc->dirn = lnws->dirn; + if (lnws->callback) { + desc->callback = lnws->callback; + desc->callback_param = lnws->callback_param; + dma_dbg("Callback passed... setting\n"); + } else + desc->callback = NULL; + return &desc->txd; + +err_desc_get: + dma_err("Failed to get desc \n"); + lnwc_desc_put(lnwc, desc); + return NULL; +} + + +static void lnw_dma2_free_chan_resources(struct dma_chan *chan) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + struct lnwdma_device *lnw = to_lnwdma_device(chan->device); + struct lnw_dma_desc *desc, *_desc; + + dma_dbg("..called for ch_id %d, lnwch_id %d\n", + chan->chan_id, lnwc->ch_id); + if (true == lnwc->in_use) { + /*trying to free ch in use!!!!!*/ + dma_err("trying to free ch in use \n"); + } + + spin_lock_bh(&lnwc->lock); + lnwc->descs_allocated = 0; + list_for_each_entry_safe(desc, _desc, &lnwc->active_list, desc_node) { + dma_dbg("del active \n"); + list_del(&desc->desc_node); + pci_pool_free(lnw->dma_pool, desc, desc->txd.phys); + } + list_for_each_entry_safe(desc, _desc, &lnwc->free_list, desc_node) { + list_del(&desc->desc_node); + pci_pool_free(lnw->dma_pool, desc, desc->txd.phys); + } + list_for_each_entry_safe(desc, _desc, &lnwc->queue, desc_node) { + dma_dbg("del queue \n"); + list_del(&desc->desc_node); + pci_pool_free(lnw->dma_pool, desc, desc->txd.phys); + } + spin_unlock_bh(&lnwc->lock); + lnwc->in_use = false; + chan->client_count--; + /* Disable CH interrupts*/ + iowrite32(MASK_INTR_REG(lnwc->ch_id), lnw->dma_base + MASK_BLOCK); + iowrite32(MASK_INTR_REG(lnwc->ch_id), lnw->dma_base + MASK_ERR); + dma_dbg("done \n"); +} + +static int lnw_dma2_alloc_chan_resources(struct dma_chan *chan) +{ + struct lnw_dma_chan *lnwc = to_lnw_dma_chan(chan); + struct lnwdma_device *lnw = to_lnwdma_device(chan->device); + struct lnw_dma_desc *desc; + dma_addr_t phys; + int i = 0; + + dma_dbg("called \n"); + + /* ASSERT: channel is idle */ + if (test_ch_en(lnw->dma_base, lnwc->ch_id)) { + /*ch is not idle*/ + dma_err(".ch not idle\n"); + return -EIO; + } + dma_dbg("..called for ch_id %d, lnwch_id %d\n", + chan->chan_id, lnwc->ch_id); + lnwc->completed = chan->cookie = 1; + + chan->client_count++; + + spin_lock_bh(&lnwc->lock); + while (lnwc->descs_allocated < DESCS_PER_CHANNEL) { + spin_unlock_bh(&lnwc->lock); + desc = pci_pool_alloc(lnw->dma_pool, GFP_KERNEL, &phys); + if (!desc) { + dma_err("desc failed\n"); + return -ENOMEM; + /*check*/ + } + dma_async_tx_descriptor_init(&desc->txd, chan); + desc->txd.tx_submit = lnw_dma2_tx_submit; + desc->txd.flags = DMA_CTRL_ACK; + desc->txd.phys = phys; + spin_lock_bh(&lnwc->lock); + i = ++lnwc->descs_allocated; + list_add_tail(&desc->desc_node, &lnwc->free_list); + } + spin_unlock_bh(&lnwc->lock); + lnwc->in_use = false; + dma_dbg("Desc alloc done ret: %d desc\n", i); + return i; +} + +static void lnwc_handle_error(struct lnwdma_device *lnw, + struct lnw_dma_chan *lnwc) +{ + lnwc_scan_descriptors(lnw, lnwc); +} + +/****************************************************************************** +* PCI stuff +*/ +static struct pci_device_id lnw_dma2_ids[] = { + { PCI_VENDOR_ID_INTEL, 0x0813, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, lnw_dma2_ids); + +static struct pci_driver lnw_dma2_pci = { + .name = "Intel LNW DMA2", + .id_table = lnw_dma2_ids, + .probe = lnw_dma2_probe, + .remove = __devexit_p(lnw_dma2_remove), +}; + +static void dma_tasklet(unsigned long data) +{ + struct lnwdma_device *lnw = NULL; + struct lnw_dma_chan *lnwc = NULL; + u32 status; + int i, ch_no; + + dma_dbg("called \n"); + lnw = (struct lnwdma_device *)data; + if (lnw == NULL) { + dma_err("Null param \n"); + return; + } + + status = ioread32(lnw->dma_base + RAW_TFR); + dma_dbg("RAW_TFR %x \n", status); + while (status) { + /*txn interrupt*/ + ch_no = get_ch_num(&status); + if (ch_no < 0) { + dma_err("Ch no is invalid %x, abort!\n", ch_no); + return; + } + dma_dbg("Got Ch %x, new Status %x \n", ch_no, status); + i = get_ch_index(ch_no); + if (i < 0) { + dma_err("Invalid ch index %x\n", i); + return; + } + dma_dbg("Tx complete interrupt %x, Ch No %d Index %d \n", + status, ch_no, i); + lnwc = &lnw->ch[i]; + if (lnwc == NULL) { + dma_err("Null param lnwc\n"); + return; + } + dma_dbg("CH %x \n", lnwc->ch_id); + spin_lock_bh(&lnwc->lock); + lnwc_scan_descriptors(lnw, lnwc); + dma_dbg("Scan of desc... complete, unmasking\n"); + iowrite32((1 << lnwc->ch_id), + lnw->dma_base + CLEAR_TFR); + dma_dbg("Wrote to clear %x\n", (1 << lnwc->ch_id)); + iowrite32((1 << lnwc->ch_id), + lnw->dma_base + CLEAR_BLOCK); + iowrite32(UNMASK_INTR_REG(lnwc->ch_id), + lnw->dma_base + MASK_TFR); + spin_unlock_bh(&lnwc->lock); + } + + dma_dbg("Trf interrupt done... \n"); + status = ioread32(lnw->dma_base + RAW_ERR); + while (status) { + /*err interrupt*/ + ch_no = get_ch_num(&status); + if (ch_no < 0) { + dma_err("Ch no is invalid %x, abort!\n", ch_no); + return; + } + dma_dbg("Got Ch %x, new Status %x \n", ch_no, status); + i = get_ch_index(ch_no); + if (i < 0) { + dma_err("Invalid CH lnwc\n"); + return; + } + dma_dbg("Tx error interrupt %x, No %d Index %d \n", + status, ch_no, i); + lnwc = &lnw->ch[i]; + if (lnwc == NULL) { + dma_err("Null param lnwc\n"); + return; + } + spin_lock_bh(&lnwc->lock); + lnwc_handle_error(lnw, lnwc); + iowrite32((1 << lnwc->ch_id), + lnw->dma_base + CLEAR_ERR); + iowrite32(UNMASK_INTR_REG(lnwc->ch_id), + lnw->dma_base + MASK_ERR); + spin_unlock_bh(&lnwc->lock); + } + dma_dbg("Exiting takslet... \n"); + return; +} + +static irqreturn_t lnw_dma2_interrupt(int irq, void *data) +{ + struct lnw_device *lnw = data; + u32 status; + int call_tasklet = 0; + + /*will mask interrupt for now and schedule tasklet + tasklet shud unmask and clear*/ + status = ioread32(lnw->dma_base + STATUS_TFR); + status &= 0x03; + if (status) { + iowrite32((status << 8), lnw->dma_base + MASK_TFR); + call_tasklet = 1; + } + status = ioread32(lnw->dma_base + STATUS_ERR); + status &= 0x03; + if (status) { + iowrite32(MASK_INTR_REG(status), lnw->dma_base + MASK_ERR); + call_tasklet = 1; + } + + if (call_tasklet) + tasklet_schedule(&lnw->dma->tasklet); + + return IRQ_HANDLED; +} + +static void enable_dma2_interrupt(struct lnw_dma_chan *lnwc) +{ + dma_dbg("Called for ch_id %d\n", lnwc->ch_id); + + iowrite32(REG_BIT0, lnwc->dma->dma_base + DMA_CFG); + /*en ch interrupts */ + iowrite32(UNMASK_INTR_REG(lnwc->ch_id), lnwc->dma_base + MASK_TFR); + iowrite32(UNMASK_INTR_REG(lnwc->ch_id), lnwc->dma_base + MASK_ERR); + return; +} + +static void disable_dma2_interrupt(struct lnw_device *device) +{ + u32 status = 0; + + /*todo*/ + dma_dbg(" called \n"); + status = 1; + return; + +} + +static int lnw_setup_dma2(struct pci_dev *pdev) +{ + struct lnw_device *device = pci_get_drvdata(pdev); + struct lnwdma_device *dma = NULL; + int err, i; + + dma_dbg("setup_dma called \n"); + dma = kzalloc(sizeof(*dma), GFP_KERNEL); + if (NULL == dma) { + dma_err("kzalloc failed \n"); + err = -ENOMEM; + goto err_kzalloc; + } + device->dma = dma; + dma->pdev = pdev; + dma->dma_base = device->dma_base; + + /* DMA coherent memory pool for DMA descriptor allocations */ + dma->dma_pool = pci_pool_create("dma_desc_pool", pdev, + sizeof(struct lnw_dma_desc), + 32, 0); + if (NULL == dma->dma_pool) { + dma_err("pci_pool_create failed \n"); + err = -ENOMEM; + kfree(dma); + goto err_dma_pool; + } + + INIT_LIST_HEAD(&dma->common.channels); + + + /*init CH structures*/ + for (i = 0; i < MAX_CHAN; i++) { + struct lnw_dma_chan *lnwch = &dma->ch[i]; + + lnwch->chan.device = &dma->common; + lnwch->chan.cookie = 1; + lnwch->chan.chan_id = i; + lnwch->ch_id = get_ch_id(i); + dma_dbg("Init CH %d, ID %d \n", i, lnwch->ch_id); + + lnwch->dma_base = dma->dma_base; + lnwch->ch_regs = dma->dma_base + DMA_CH_SIZE * lnwch->ch_id; + lnwch->dma = dma; + spin_lock_init(&lnwch->lock); + + INIT_LIST_HEAD(&lnwch->active_list); + INIT_LIST_HEAD(&lnwch->queue); + INIT_LIST_HEAD(&lnwch->free_list); + + /*mask interrupts*/ + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_BLOCK); + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_SRC_TRAN); + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_DST_TRAN); + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_ERR); + iowrite32(MASK_INTR_REG(lnwch->ch_id), + dma->dma_base + MASK_TFR); + + dma_dbg("Init CH %d, ID %d \n", i, lnwch->ch_id); + list_add_tail(&lnwch->chan.device_node, &dma->common.channels); + } + + /*init dma structure*/ + dma_cap_zero(dma->common.cap_mask); + dma_cap_set(DMA_MEMCPY, dma->common.cap_mask); + dma_cap_set(DMA_SLAVE, dma->common.cap_mask); + dma_cap_set(DMA_PRIVATE, dma->common.cap_mask); + dma->common.dev = &pdev->dev; + dma->common.chancnt = MAX_CHAN; + + dma->common.device_alloc_chan_resources = + lnw_dma2_alloc_chan_resources; + dma->common.device_free_chan_resources = + lnw_dma2_free_chan_resources; + + dma->common.device_is_tx_complete = lnw_dma2_tx_is_complete; + dma->common.device_prep_dma_memcpy = lnw_dma2_prep_memcpy; + dma->common.device_issue_pending = lnw_dma2_issue_pending; + dma->common.device_prep_slave_sg = lnw_dma2_prep_slave_sg; + dma->common.device_terminate_all = lnw_dma2_terminate_all; + + /*enable dma cntrl*/ + iowrite32(REG_BIT0, dma->dma_base + DMA_CFG); + + disable_dma2_interrupt(device); + + /*register irq*/ + err = request_irq(pdev->irq, lnw_dma2_interrupt, + 0, lnw_dma2_pci.name, device); + if (0 != err) + goto err_irq; + + /*register device w/ engine*/ + err = dma_async_device_register(&dma->common); + if (0 != err) { + dma_err("device_register failed: %d \n", err); + goto err_engine; + } + tasklet_init(&dma->tasklet, dma_tasklet, (unsigned long)dma); + dma_dbg("...done\n"); + return 0; + +err_engine: + free_irq(pdev->irq, device); +err_irq: + pci_pool_destroy(dma->dma_pool); + kfree(dma); +err_dma_pool: +err_kzalloc: + dma_err("setup_dma failed: %d \n", err); + return err; + +} + +static void lnwdma_shutdown(struct pci_dev *pdev) +{ + struct lnw_device *device = pci_get_drvdata(pdev); + + dma_dbg("shutdown called \n"); + dma_async_device_unregister(&device->dma->common); + pci_pool_destroy(device->dma->dma_pool); + if (device->dma_base) + iounmap(device->dma_base); + free_irq(pdev->irq, device); + return; +} +static int __devinit +lnw_dma2_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct lnw_device *device = NULL; + u32 base_addr = 0, bar_size = 0; + int err = 0; + + dma_info("probe called for %x \n", pdev->device); + err = pci_enable_device(pdev); + if (err) + goto err_enable_device; + + err = pci_request_regions(pdev, lnw_dma2_pci.name); + if (err) + goto err_request_regions; + + err = pci_set_dma_mask(pdev, DMA_32BIT_MASK); + if (err) + goto err_set_dma_mask; + + err = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK); + if (err) + goto err_set_dma_mask; + + device = kzalloc(sizeof(*device), GFP_KERNEL); + if (!device) { + dma_err("kzalloc failed \n"); + err = -ENOMEM; + goto err_kzalloc; + } + device->pdev = pci_dev_get(pdev); + + base_addr = pci_resource_start(pdev, 0); + bar_size = pci_resource_len(pdev, 0); + dma_dbg("BAR0 %x Size %x \n", base_addr, bar_size); + device->dma_base = ioremap_nocache(base_addr, DMA_REG_SIZE); + if (!device->dma_base) { + dma_err("ioremap failed \n"); + err = -ENOMEM; + goto err_ioremap; + } + + pci_set_drvdata(pdev, device); + pci_set_master(pdev); + + err = lnw_setup_dma2(pdev); + if (err) + goto err_dma; + + return 0; + +err_dma: + iounmap(device->dma_base); +err_ioremap: + pci_dev_put(pdev); + kfree(device); +err_kzalloc: +err_set_dma_mask: + pci_release_regions(pdev); + pci_disable_device(pdev); +err_request_regions: +err_enable_device: + dma_err("Probe failed %d\n", err); + return err; +} + +static void __devexit lnw_dma2_remove(struct pci_dev *pdev) +{ + struct lnw_device *device = pci_get_drvdata(pdev); + + lnwdma_shutdown(pdev); + pci_dev_put(pdev); + kfree(device); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static int __init lnw_dma2_init(void) +{ + dma_info("LNW DMA Driver\n Version %s \n", LNW_DMA_DRIVER_VERSION); + return pci_register_driver(&lnw_dma2_pci); +} +fs_initcall(lnw_dma2_init); + +static void __exit lnw_dma2_exit(void) +{ + pci_unregister_driver(&lnw_dma2_pci); +} +module_exit(lnw_dma2_exit); + Index: linux-2.6.33/include/linux/lnw_dma.h =================================================================== --- /dev/null +++ linux-2.6.33/include/linux/lnw_dma.h @@ -0,0 +1,166 @@ +/* + * lnw_dma.c - Intel Langwell DMA Drivers + * + * Copyright (C) 2008i-09 Intel Corp + * Author: Vinod Koul + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#ifndef __LNW_DMA_H__ +#define __LNW_DMA_H__ + +#include + +/*DMA transaction width, src and dstn width would be same +The DMA length must be width aligned, +for 32 bit width the length must be 32 bit (4bytes) aligned only*/ +enum lnw_dma_width { + LNW_DMA_WIDTH_8BIT = 0x0, + LNW_DMA_WIDTH_16BIT = 0x1, + LNW_DMA_WIDTH_32BIT = 0x2, +}; + +/*DMA mode configurations*/ +enum lnw_dma_mode { + LNW_DMA_PER_TO_MEM = 0, /*periphral to memory configuration*/ + LNW_DMA_MEM_TO_PER, /*memory to periphral configuration*/ + LNW_DMA_MEM_TO_MEM, /*mem to mem confg (testing only)*/ +}; + +/*DMA handshaking*/ +enum lnw_dma_hs_mode { + LNW_DMA_HW_HS = 0, /*HW Handshaking only*/ + LNW_DMA_SW_HS = 1, /*SW Handshaking not recommended*/ +}; + +/*Burst size configuration*/ +enum lnw_dma_msize { + LNW_DMA_MSIZE_1 = 0x0, + LNW_DMA_MSIZE_4 = 0x1, + LNW_DMA_MSIZE_8 = 0x2, + LNW_DMA_MSIZE_16 = 0x3, + LNW_DMA_MSIZE_32 = 0x4, + LNW_DMA_MSIZE_64 = 0x5, +}; + +/** + * struct lnw_dma_slave - DMA slave structure + * + * @dma_dev: DMA master client + * @tx_reg: physical address of data register used for + * memory-to-peripheral transfers + * @rx_reg: physical address of data register used for + * peripheral-to-memory transfers + * @tx_width: tx register width + * @rx_width: rx register width + * @dirn: DMA trf direction + + * @cfg_hi: Platform-specific initializer for the CFG_HI register + * @cfg_lo: Platform-specific initializer for the CFG_LO register + + * @ tx_width: width of src and dstn + * @ hs_mode: SW or HW handskaking mode + * @ cfg_mode: Mode configuration, DMA mem to mem to dev & mem + */ +struct lnw_dma_slave { + enum dma_data_direction dirn; + enum lnw_dma_width src_width; /*width of DMA src txn*/ + enum lnw_dma_width dst_width; /*width of DMA dst txn*/ + enum lnw_dma_hs_mode hs_mode; /*handshaking*/ + enum lnw_dma_mode cfg_mode; /*mode configuration*/ + enum lnw_dma_msize src_msize; /*size if src burst*/ + enum lnw_dma_msize dst_msize; /*size of dst burst*/ + dma_async_tx_callback callback; /*callback function*/ + void *callback_param; /*param for callback*/ +}; + +/*DMA channel control registers*/ +union lnw_dma_ctl_lo { + struct { + u32 int_en:1; /*enable or disable interrupts*/ + /*should be 0*/ + u32 dst_tr_width:3; /*destination transfer width*/ + /*usually 32 bits = 010*/ + u32 src_tr_width:3; /*source transfer width*/ + /*usually 32 bits = 010*/ + u32 dinc:2; /*destination address inc/dec*/ + /*For mem:INC=00, Periphral NoINC=11*/ + u32 sinc:2; /*source address inc or dec, as above*/ + u32 dst_msize:3; /*destination burst transaction length*/ + /*always = 16 ie 011*/ + u32 src_msize:3; /*source burst transaction length*/ + /*always = 16 ie 011*/ + u32 reser1:3; + u32 tt_fc:3; /*transfer type and flow controller*/ + /*M-M = 000 + P-M = 010 + M-P = 001*/ + u32 dms:2; /*destination master select = 0*/ + u32 sms:2; /*source master select = 0*/ + u32 llp_dst_en:1; /*enable/disable destination LLP = 0*/ + u32 llp_src_en:1; /*enable/disable source LLP = 0*/ + u32 reser2:3; + } ctlx; + u32 ctl_lo; +}; + +union lnw_dma_ctl_hi { + struct { + u32 block_ts:12; /*block transfer size*/ + /*configured by DMAC*/ + u32 reser:20; + } ctlx; + u32 ctl_hi; + +}; + +/*DMA channel configuration registers*/ +union lnw_dma_cfg_lo { + struct { + u32 reser1:5; + u32 ch_prior:3; /*channel priority = 0*/ + u32 ch_susp:1; /*channel suspend = 0*/ + u32 fifo_empty:1; /*FIFO empty or not R bit = 0*/ + u32 hs_sel_dst:1; /*select HW/SW destn handshaking*/ + /*HW = 0, SW = 1*/ + u32 hs_sel_src:1; /*select HW/SW src handshaking*/ + u32 reser2:6; + u32 dst_hs_pol:1; /*dest HS interface polarity*/ + u32 src_hs_pol:1; /*src HS interface polarity*/ + u32 max_abrst:10; /*max AMBA burst len = 0 (no sw limit*/ + u32 reload_src:1; /*auto reload src addr =1 if src is P*/ + u32 reload_dst:1; /*AR destn addr =1 if dstn is P*/ + } cfgx; + u32 cfg_lo; +}; + +union lnw_dma_cfg_hi { + struct { + u32 fcmode:1; /*flow control mode = 1*/ + u32 fifo_mode:1; /*FIFO mode select = 1*/ + u32 protctl:3; /*protection control = 0*/ + u32 rsvd:2; + u32 src_per:4; /*src hw HS interface*/ + u32 dst_per:4; /*dstn hw HS interface*/ + u32 reser2:17; + } cfgx; + u32 cfg_hi; +}; + +#endif /*__LNW_DMA_H__*/ Index: linux-2.6.33/include/linux/intel_mid.h =================================================================== --- /dev/null +++ linux-2.6.33/include/linux/intel_mid.h @@ -0,0 +1,144 @@ +/* + * intel_mid.h - Netlink multicast interface definition for OSPM. + * + * Copyright (C) 2009 Intel Corp + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Authors: Sujith Thomas + * Rajeev D Muralidhar + * Vishwesh M Rudramuni + * Nithish Mahalingam + * Contact information: + * Sujith Thomas + * Rajeev D Muralidhar + * Vishwesh M Rudramuni + * Nithish Mahalingam + * + */ + +#ifndef INTEL_MID_H +#define INTEL_MID_H + +#define PMU1_MAX_DEVS 2 +#define PMU2_MAX_DEVS 12 +#define PERIPH_MAX_DEVS 3 +#define MAX_DEVICES (PMU1_MAX_DEVS + PMU2_MAX_DEVS + PERIPH_MAX_DEVS) +#define WAKE_CAPABLE 0x80000000 + +struct pci_dev_info { + u16 vendor_id; + u16 device_id; + u16 log_subsysid; + u16 phy_susbsysid; + u32 capability; + struct pci_dev *dev_driver; + char *dev_name; +}; + +struct mid_ospm { + u32 *pmu1_base; + u32 *pmu1_pm_base; + void __iomem *pmu2_base; + u32 *pm_table_base; + u32 pmu1_sub_systems; + u32 pmu2_sub_systems; + u32 pmu_wake_cfg; + u32 pmu_wake_ss_states; + u32 perepheral_sub_systems; + int pmu2_states; + int platform_sx_state; + int s0ix_retry_enb; + int fast_retry_exit; + u32 pmode; +}; + +extern struct pci_dev_info platform_pci_devices[MAX_DEVICES]; +extern unsigned long g_intel_mid_wakeup_address; + +enum pmu_ss_state { + SS_STATE_D0I0 = 0, + SS_STATE_D0I1 = 1, + SS_STATE_D0I2 = 2, + SS_STATE_D0I3 = 3 +}; + +enum eospm_events { + OSPM_EVENT_SUBSYS_INACTIVITY, + OSPM_EVENT_SUBSYS_WAKE, + OSPM_EVENT_SUBSYS_START_PLAY, + OSPM_EVENT_SUBSYS_STOP_PLAY, + OSPM_EVENT_CMD_SUCCESS, + OSPM_EVENT_CMD_ERROR, + OSPM_EVENT_CMD_NO_C6_ERROR, + OSPM_EVENT_AUDIO_BUF_EMPTY, + OSPM_EVENT_AUDIO_BUF_FULL, + OSPM_EVENT_THERMAL_AUX0, + OSPM_EVENT_THERMAL_AUX1, + OSPM_EVENT_THERMAL_CRITICAL, + OSPM_EVENT_THERMAL_DEV_FAULT, + __OSPM_EVENT_COUNT, +}; + +#define AUDIO_SUBSYTEM_ID 25 +#define MID_S0I1_STATE 1 +#define MID_S0I3_STATE 3 +/* Thermal device Id */ +#define TEMP_DEV_ID1 40 +#define TEMP_DEV_ID2 41 +#define TEMP_DEV_ID3 42 + +/* First 32 (0-31) originators are subsystems + Next 8 (0-7) are cmd IDs */ +#define OSPM_CMDID_OFFSET 32 +#define OSPM_MAX_CMD_ID 8 + +struct ospm_genl_event { + u32 orig; + enum eospm_events event; +}; + +/* attributes of ospm_genl_family */ +enum { + OSPM_GENL_ATTR_UNSPEC, + OSPM_GENL_ATTR_EVENT, /* OSPM event info needed by user space */ + __OSPM_GENL_ATTR_MAX, +}; +#define OSPM_GENL_ATTR_MAX (__OSPM_GENL_ATTR_MAX - 1) + +/* commands supported by the ospm_genl_family */ + +enum { + OSPM_GENL_CMD_UNSPEC, + OSPM_GENL_CMD_EVENT, /* kernel->user notifications for OSPM events */ + __OSPM_GENL_CMD_MAX, +}; +#define OSPM_GENL_CMD_MAX (__OSPM_GENL_CMD_MAX - 1) + +#define OSPM_GENL_FAMILY_NAME "ospm_event" +#define OSPM_GENL_VERSION 0x01 +#define OSPM_GENL_MCAST_GROUP_NAME "ospm_mc_group" + +int ospm_generate_netlink_event(u32 orig, enum eospm_events event); +int ospm_event_genetlink_init(void); +void ospm_event_genetlink_exit(void); + +extern void intel_mid_reserve_bootmem(void); +extern unsigned long g_intel_mid_wakeup_address; +extern void find_pci_info(u32 device_id, u32 vendor_id, u32 *index); +extern int s0ix_non_bsp_init(void); + +#endif