Path: blob/master/drivers/dma/loongson/loongson1-apb-dma.c
170890 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* Driver for Loongson-1 APB DMA Controller3*4* Copyright (C) 2015-2024 Keguang Zhang <[email protected]>5*/67#include <linux/dmapool.h>8#include <linux/dma-mapping.h>9#include <linux/init.h>10#include <linux/interrupt.h>11#include <linux/iopoll.h>12#include <linux/module.h>13#include <linux/of.h>14#include <linux/of_dma.h>15#include <linux/platform_device.h>16#include <linux/slab.h>1718#include "../dmaengine.h"19#include "../virt-dma.h"2021/* Loongson-1 DMA Control Register */22#define LS1X_DMA_CTRL 0x02324/* DMA Control Register Bits */25#define LS1X_DMA_STOP BIT(4)26#define LS1X_DMA_START BIT(3)27#define LS1X_DMA_ASK_VALID BIT(2)2829/* DMA Next Field Bits */30#define LS1X_DMA_NEXT_VALID BIT(0)3132/* DMA Command Field Bits */33#define LS1X_DMA_RAM2DEV BIT(12)34#define LS1X_DMA_INT BIT(1)35#define LS1X_DMA_INT_MASK BIT(0)3637#define LS1X_DMA_LLI_ALIGNMENT 6438#define LS1X_DMA_LLI_ADDR_MASK GENMASK(31, __ffs(LS1X_DMA_LLI_ALIGNMENT))39#define LS1X_DMA_MAX_CHANNELS 34041enum ls1x_dmadesc_offsets {42LS1X_DMADESC_NEXT = 0,43LS1X_DMADESC_SADDR,44LS1X_DMADESC_DADDR,45LS1X_DMADESC_LENGTH,46LS1X_DMADESC_STRIDE,47LS1X_DMADESC_CYCLES,48LS1X_DMADESC_CMD,49LS1X_DMADESC_SIZE50};5152struct ls1x_dma_lli {53unsigned int hw[LS1X_DMADESC_SIZE];54dma_addr_t phys;55struct list_head node;56} __aligned(LS1X_DMA_LLI_ALIGNMENT);5758struct ls1x_dma_desc {59struct virt_dma_desc vd;60struct list_head lli_list;61};6263struct ls1x_dma_chan {64struct virt_dma_chan vc;65struct dma_pool *lli_pool;66phys_addr_t src_addr;67phys_addr_t dst_addr;68enum dma_slave_buswidth src_addr_width;69enum dma_slave_buswidth dst_addr_width;70unsigned int bus_width;71void __iomem *reg_base;72int irq;73bool is_cyclic;74struct ls1x_dma_lli *curr_lli;75};7677struct ls1x_dma {78struct dma_device ddev;79unsigned int nr_chans;80struct ls1x_dma_chan chan[];81};8283static irqreturn_t ls1x_dma_irq_handler(int irq, void *data);8485#define to_ls1x_dma_chan(dchan) \86container_of(dchan, struct ls1x_dma_chan, vc.chan)8788#define to_ls1x_dma_desc(d) \89container_of(d, struct ls1x_dma_desc, vd)9091static inline struct device *chan2dev(struct dma_chan *chan)92{93return &chan->dev->device;94}9596static inline int ls1x_dma_query(struct ls1x_dma_chan *chan,97dma_addr_t *lli_phys)98{99struct dma_chan *dchan = &chan->vc.chan;100int val, ret;101102val = *lli_phys & LS1X_DMA_LLI_ADDR_MASK;103val |= LS1X_DMA_ASK_VALID;104val |= dchan->chan_id;105writel(val, chan->reg_base + LS1X_DMA_CTRL);106ret = readl_poll_timeout_atomic(chan->reg_base + LS1X_DMA_CTRL, val,107!(val & LS1X_DMA_ASK_VALID), 0, 3000);108if (ret)109dev_err(chan2dev(dchan), "failed to query DMA\n");110111return ret;112}113114static inline int ls1x_dma_start(struct ls1x_dma_chan *chan,115dma_addr_t *lli_phys)116{117struct dma_chan *dchan = &chan->vc.chan;118struct device *dev = chan2dev(dchan);119int val, ret;120121val = *lli_phys & LS1X_DMA_LLI_ADDR_MASK;122val |= LS1X_DMA_START;123val |= dchan->chan_id;124writel(val, chan->reg_base + LS1X_DMA_CTRL);125ret = readl_poll_timeout(chan->reg_base + LS1X_DMA_CTRL, val,126!(val & LS1X_DMA_START), 0, 1000);127if (!ret)128dev_dbg(dev, "start DMA with lli_phys=%pad\n", lli_phys);129else130dev_err(dev, "failed to start DMA\n");131132return ret;133}134135static inline void ls1x_dma_stop(struct ls1x_dma_chan *chan)136{137int val = readl(chan->reg_base + LS1X_DMA_CTRL);138139writel(val | LS1X_DMA_STOP, chan->reg_base + LS1X_DMA_CTRL);140}141142static void ls1x_dma_free_chan_resources(struct dma_chan *dchan)143{144struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);145struct device *dev = chan2dev(dchan);146147dma_free_coherent(dev, sizeof(struct ls1x_dma_lli),148chan->curr_lli, chan->curr_lli->phys);149dma_pool_destroy(chan->lli_pool);150chan->lli_pool = NULL;151devm_free_irq(dev, chan->irq, chan);152vchan_free_chan_resources(&chan->vc);153}154155static int ls1x_dma_alloc_chan_resources(struct dma_chan *dchan)156{157struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);158struct device *dev = chan2dev(dchan);159dma_addr_t phys;160int ret;161162ret = devm_request_irq(dev, chan->irq, ls1x_dma_irq_handler,163IRQF_SHARED, dma_chan_name(dchan), chan);164if (ret) {165dev_err(dev, "failed to request IRQ %d\n", chan->irq);166return ret;167}168169chan->lli_pool = dma_pool_create(dma_chan_name(dchan), dev,170sizeof(struct ls1x_dma_lli),171__alignof__(struct ls1x_dma_lli), 0);172if (!chan->lli_pool)173return -ENOMEM;174175/* allocate memory for querying the current lli */176dma_set_coherent_mask(dev, DMA_BIT_MASK(32));177chan->curr_lli = dma_alloc_coherent(dev, sizeof(struct ls1x_dma_lli),178&phys, GFP_KERNEL);179if (!chan->curr_lli) {180dma_pool_destroy(chan->lli_pool);181return -ENOMEM;182}183chan->curr_lli->phys = phys;184185return 0;186}187188static void ls1x_dma_free_desc(struct virt_dma_desc *vd)189{190struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vd);191struct ls1x_dma_chan *chan = to_ls1x_dma_chan(vd->tx.chan);192struct ls1x_dma_lli *lli, *_lli;193194list_for_each_entry_safe(lli, _lli, &desc->lli_list, node) {195list_del(&lli->node);196dma_pool_free(chan->lli_pool, lli, lli->phys);197}198199kfree(desc);200}201202static struct ls1x_dma_desc *ls1x_dma_alloc_desc(void)203{204struct ls1x_dma_desc *desc;205206desc = kzalloc_obj(*desc, GFP_NOWAIT);207if (!desc)208return NULL;209210INIT_LIST_HEAD(&desc->lli_list);211212return desc;213}214215static int ls1x_dma_prep_lli(struct dma_chan *dchan, struct ls1x_dma_desc *desc,216struct scatterlist *sgl, unsigned int sg_len,217enum dma_transfer_direction dir, bool is_cyclic)218{219struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);220struct ls1x_dma_lli *lli, *prev = NULL, *first = NULL;221struct device *dev = chan2dev(dchan);222struct list_head *pos = NULL;223struct scatterlist *sg;224unsigned int dev_addr, cmd, i;225226switch (dir) {227case DMA_MEM_TO_DEV:228dev_addr = chan->dst_addr;229chan->bus_width = chan->dst_addr_width;230cmd = LS1X_DMA_RAM2DEV | LS1X_DMA_INT;231break;232case DMA_DEV_TO_MEM:233dev_addr = chan->src_addr;234chan->bus_width = chan->src_addr_width;235cmd = LS1X_DMA_INT;236break;237default:238dev_err(dev, "unsupported DMA direction: %s\n",239dmaengine_get_direction_text(dir));240return -EINVAL;241}242243for_each_sg(sgl, sg, sg_len, i) {244dma_addr_t buf_addr = sg_dma_address(sg);245size_t buf_len = sg_dma_len(sg);246dma_addr_t phys;247248if (!is_dma_copy_aligned(dchan->device, buf_addr, 0, buf_len)) {249dev_err(dev, "buffer is not aligned\n");250return -EINVAL;251}252253/* allocate HW descriptors */254lli = dma_pool_zalloc(chan->lli_pool, GFP_NOWAIT, &phys);255if (!lli) {256dev_err(dev, "failed to alloc lli %u\n", i);257return -ENOMEM;258}259260/* setup HW descriptors */261lli->phys = phys;262lli->hw[LS1X_DMADESC_SADDR] = buf_addr;263lli->hw[LS1X_DMADESC_DADDR] = dev_addr;264lli->hw[LS1X_DMADESC_LENGTH] = buf_len / chan->bus_width;265lli->hw[LS1X_DMADESC_STRIDE] = 0;266lli->hw[LS1X_DMADESC_CYCLES] = 1;267lli->hw[LS1X_DMADESC_CMD] = cmd;268269if (prev)270prev->hw[LS1X_DMADESC_NEXT] =271lli->phys | LS1X_DMA_NEXT_VALID;272prev = lli;273274if (!first)275first = lli;276277list_add_tail(&lli->node, &desc->lli_list);278}279280if (is_cyclic) {281lli->hw[LS1X_DMADESC_NEXT] = first->phys | LS1X_DMA_NEXT_VALID;282chan->is_cyclic = is_cyclic;283}284285list_for_each(pos, &desc->lli_list) {286lli = list_entry(pos, struct ls1x_dma_lli, node);287print_hex_dump_debug("LLI: ", DUMP_PREFIX_OFFSET, 16, 4,288lli, sizeof(*lli), false);289}290291return 0;292}293294static struct dma_async_tx_descriptor *295ls1x_dma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,296unsigned int sg_len, enum dma_transfer_direction dir,297unsigned long flags, void *context)298{299struct ls1x_dma_desc *desc;300301dev_dbg(chan2dev(dchan), "sg_len=%u flags=0x%lx dir=%s\n",302sg_len, flags, dmaengine_get_direction_text(dir));303304desc = ls1x_dma_alloc_desc();305if (!desc)306return NULL;307308if (ls1x_dma_prep_lli(dchan, desc, sgl, sg_len, dir, false)) {309ls1x_dma_free_desc(&desc->vd);310return NULL;311}312313return vchan_tx_prep(to_virt_chan(dchan), &desc->vd, flags);314}315316static struct dma_async_tx_descriptor *317ls1x_dma_prep_dma_cyclic(struct dma_chan *dchan, dma_addr_t buf_addr,318size_t buf_len, size_t period_len,319enum dma_transfer_direction dir, unsigned long flags)320{321struct ls1x_dma_desc *desc;322struct scatterlist *sgl;323unsigned int sg_len;324unsigned int i;325int ret;326327dev_dbg(chan2dev(dchan),328"buf_len=%zu period_len=%zu flags=0x%lx dir=%s\n",329buf_len, period_len, flags, dmaengine_get_direction_text(dir));330331desc = ls1x_dma_alloc_desc();332if (!desc)333return NULL;334335/* allocate the scatterlist */336sg_len = buf_len / period_len;337sgl = kmalloc_objs(*sgl, sg_len, GFP_NOWAIT);338if (!sgl)339return NULL;340341sg_init_table(sgl, sg_len);342for (i = 0; i < sg_len; ++i) {343sg_set_page(&sgl[i], pfn_to_page(PFN_DOWN(buf_addr)),344period_len, offset_in_page(buf_addr));345sg_dma_address(&sgl[i]) = buf_addr;346sg_dma_len(&sgl[i]) = period_len;347buf_addr += period_len;348}349350ret = ls1x_dma_prep_lli(dchan, desc, sgl, sg_len, dir, true);351kfree(sgl);352if (ret) {353ls1x_dma_free_desc(&desc->vd);354return NULL;355}356357return vchan_tx_prep(to_virt_chan(dchan), &desc->vd, flags);358}359360static int ls1x_dma_slave_config(struct dma_chan *dchan,361struct dma_slave_config *config)362{363struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);364365chan->src_addr = config->src_addr;366chan->src_addr_width = config->src_addr_width;367chan->dst_addr = config->dst_addr;368chan->dst_addr_width = config->dst_addr_width;369370return 0;371}372373static int ls1x_dma_pause(struct dma_chan *dchan)374{375struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);376int ret;377378guard(spinlock_irqsave)(&chan->vc.lock);379/* save the current lli */380ret = ls1x_dma_query(chan, &chan->curr_lli->phys);381if (!ret)382ls1x_dma_stop(chan);383384return ret;385}386387static int ls1x_dma_resume(struct dma_chan *dchan)388{389struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);390391guard(spinlock_irqsave)(&chan->vc.lock);392393return ls1x_dma_start(chan, &chan->curr_lli->phys);394}395396static int ls1x_dma_terminate_all(struct dma_chan *dchan)397{398struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);399struct virt_dma_desc *vd;400LIST_HEAD(head);401402ls1x_dma_stop(chan);403404scoped_guard(spinlock_irqsave, &chan->vc.lock) {405vd = vchan_next_desc(&chan->vc);406if (vd)407vchan_terminate_vdesc(vd);408409vchan_get_all_descriptors(&chan->vc, &head);410}411412vchan_dma_desc_free_list(&chan->vc, &head);413414return 0;415}416417static void ls1x_dma_synchronize(struct dma_chan *dchan)418{419vchan_synchronize(to_virt_chan(dchan));420}421422static enum dma_status ls1x_dma_tx_status(struct dma_chan *dchan,423dma_cookie_t cookie,424struct dma_tx_state *state)425{426struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);427struct virt_dma_desc *vd;428enum dma_status status;429size_t bytes = 0;430431status = dma_cookie_status(dchan, cookie, state);432if (status == DMA_COMPLETE)433return status;434435scoped_guard(spinlock_irqsave, &chan->vc.lock) {436vd = vchan_find_desc(&chan->vc, cookie);437if (vd) {438struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vd);439struct ls1x_dma_lli *lli;440dma_addr_t next_phys;441442/* get the current lli */443if (ls1x_dma_query(chan, &chan->curr_lli->phys))444return status;445446/* locate the current lli */447next_phys = chan->curr_lli->hw[LS1X_DMADESC_NEXT];448list_for_each_entry(lli, &desc->lli_list, node)449if (lli->hw[LS1X_DMADESC_NEXT] == next_phys)450break;451452dev_dbg(chan2dev(dchan), "current lli_phys=%pad",453&lli->phys);454455/* count the residues */456list_for_each_entry_from(lli, &desc->lli_list, node)457bytes += lli->hw[LS1X_DMADESC_LENGTH] *458chan->bus_width;459}460}461462dma_set_residue(state, bytes);463464return status;465}466467static void ls1x_dma_issue_pending(struct dma_chan *dchan)468{469struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);470471guard(spinlock_irqsave)(&chan->vc.lock);472473if (vchan_issue_pending(&chan->vc)) {474struct virt_dma_desc *vd = vchan_next_desc(&chan->vc);475476if (vd) {477struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vd);478struct ls1x_dma_lli *lli;479480lli = list_first_entry(&desc->lli_list,481struct ls1x_dma_lli, node);482ls1x_dma_start(chan, &lli->phys);483}484}485}486487static irqreturn_t ls1x_dma_irq_handler(int irq, void *data)488{489struct ls1x_dma_chan *chan = data;490struct dma_chan *dchan = &chan->vc.chan;491struct device *dev = chan2dev(dchan);492struct virt_dma_desc *vd;493494scoped_guard(spinlock, &chan->vc.lock) {495vd = vchan_next_desc(&chan->vc);496if (!vd) {497dev_warn(dev,498"IRQ %d with no active desc on channel %d\n",499irq, dchan->chan_id);500return IRQ_NONE;501}502503if (chan->is_cyclic) {504vchan_cyclic_callback(vd);505} else {506list_del(&vd->node);507vchan_cookie_complete(vd);508}509}510511dev_dbg(dev, "DMA IRQ %d on channel %d\n", irq, dchan->chan_id);512513return IRQ_HANDLED;514}515516static int ls1x_dma_chan_probe(struct platform_device *pdev,517struct ls1x_dma *dma)518{519void __iomem *reg_base;520int id;521522reg_base = devm_platform_ioremap_resource(pdev, 0);523if (IS_ERR(reg_base))524return PTR_ERR(reg_base);525526for (id = 0; id < dma->nr_chans; id++) {527struct ls1x_dma_chan *chan = &dma->chan[id];528char pdev_irqname[16];529530snprintf(pdev_irqname, sizeof(pdev_irqname), "ch%d", id);531chan->irq = platform_get_irq_byname(pdev, pdev_irqname);532if (chan->irq < 0)533return dev_err_probe(&pdev->dev, chan->irq,534"failed to get IRQ for ch%d\n",535id);536537chan->reg_base = reg_base;538chan->vc.desc_free = ls1x_dma_free_desc;539vchan_init(&chan->vc, &dma->ddev);540}541542return 0;543}544545static void ls1x_dma_chan_remove(struct ls1x_dma *dma)546{547int id;548549for (id = 0; id < dma->nr_chans; id++) {550struct ls1x_dma_chan *chan = &dma->chan[id];551552if (chan->vc.chan.device == &dma->ddev) {553list_del(&chan->vc.chan.device_node);554tasklet_kill(&chan->vc.task);555}556}557}558559static int ls1x_dma_probe(struct platform_device *pdev)560{561struct device *dev = &pdev->dev;562struct dma_device *ddev;563struct ls1x_dma *dma;564int ret;565566ret = platform_irq_count(pdev);567if (ret <= 0 || ret > LS1X_DMA_MAX_CHANNELS)568return dev_err_probe(dev, -EINVAL,569"Invalid number of IRQ channels: %d\n",570ret);571572dma = devm_kzalloc(dev, struct_size(dma, chan, ret), GFP_KERNEL);573if (!dma)574return -ENOMEM;575dma->nr_chans = ret;576577/* initialize DMA device */578ddev = &dma->ddev;579ddev->dev = dev;580ddev->copy_align = DMAENGINE_ALIGN_4_BYTES;581ddev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |582BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |583BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);584ddev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |585BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |586BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);587ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);588ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;589ddev->device_alloc_chan_resources = ls1x_dma_alloc_chan_resources;590ddev->device_free_chan_resources = ls1x_dma_free_chan_resources;591ddev->device_prep_slave_sg = ls1x_dma_prep_slave_sg;592ddev->device_prep_dma_cyclic = ls1x_dma_prep_dma_cyclic;593ddev->device_config = ls1x_dma_slave_config;594ddev->device_pause = ls1x_dma_pause;595ddev->device_resume = ls1x_dma_resume;596ddev->device_terminate_all = ls1x_dma_terminate_all;597ddev->device_synchronize = ls1x_dma_synchronize;598ddev->device_tx_status = ls1x_dma_tx_status;599ddev->device_issue_pending = ls1x_dma_issue_pending;600dma_cap_set(DMA_SLAVE, ddev->cap_mask);601INIT_LIST_HEAD(&ddev->channels);602603/* initialize DMA channels */604ret = ls1x_dma_chan_probe(pdev, dma);605if (ret)606goto err;607608ret = dmaenginem_async_device_register(ddev);609if (ret) {610dev_err(dev, "failed to register DMA device\n");611goto err;612}613614ret = of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id,615ddev);616if (ret) {617dev_err(dev, "failed to register DMA controller\n");618goto err;619}620621platform_set_drvdata(pdev, dma);622dev_info(dev, "Loongson1 DMA driver registered\n");623624return 0;625626err:627ls1x_dma_chan_remove(dma);628629return ret;630}631632static void ls1x_dma_remove(struct platform_device *pdev)633{634struct ls1x_dma *dma = platform_get_drvdata(pdev);635636of_dma_controller_free(pdev->dev.of_node);637ls1x_dma_chan_remove(dma);638}639640static const struct of_device_id ls1x_dma_match[] = {641{ .compatible = "loongson,ls1b-apbdma" },642{ /* sentinel */ }643};644MODULE_DEVICE_TABLE(of, ls1x_dma_match);645646static struct platform_driver ls1x_dma_driver = {647.probe = ls1x_dma_probe,648.remove = ls1x_dma_remove,649.driver = {650.name = KBUILD_MODNAME,651.of_match_table = ls1x_dma_match,652},653};654655module_platform_driver(ls1x_dma_driver);656657MODULE_AUTHOR("Keguang Zhang <[email protected]>");658MODULE_DESCRIPTION("Loongson-1 APB DMA Controller driver");659MODULE_LICENSE("GPL");660661662