Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0-only
config QCOM_ADM
tristate "Qualcomm ADM support"
depends on (ARCH_QCOM || COMPILE_TEST) && !PHYS_ADDR_T_64BIT
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
help
Enable support for the Qualcomm Application Data Mover (ADM) DMA
controller, as present on MSM8x60, APQ8064, and IPQ8064 devices.
This controller provides DMA capabilities for both general purpose
and on-chip peripheral devices.
config QCOM_BAM_DMA
tristate "Qualcomm BAM DMA support"
depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM)
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
help
Enable support for the QCOM BAM DMA controller. This controller
provides DMA capabilities for a variety of on-chip devices.
config QCOM_GPI_DMA
tristate "Qualcomm GPI DMA support"
depends on ARCH_QCOM
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
help
Enable support for the QCOM GPI DMA controller. This controller
provides DMA capabilities for a variety of peripheral buses such
as I2C, UART, and SPI. By using GPI dmaengine driver, bus drivers
can use a standardize interface that is protocol independent to
transfer data between DDR and peripheral.
config QCOM_HIDMA_MGMT
tristate "Qualcomm HIDMA Management support"
depends on HAS_IOMEM
select DMA_ENGINE
help
Enable support for the Qualcomm Technologies HIDMA Management.
Each DMA device requires one management interface driver
for basic initialization before QCOM_HIDMA channel driver can
start managing the channels. In a virtualized environment,
the guest OS would run QCOM_HIDMA channel driver and the
host would run the QCOM_HIDMA_MGMT management driver.
config QCOM_HIDMA
tristate "Qualcomm HIDMA Channel support"
depends on HAS_IOMEM
select DMA_ENGINE
help
Enable support for the Qualcomm Technologies HIDMA controller.
The HIDMA controller supports optimized buffer copies
(user to kernel, kernel to kernel, etc.). It only supports
memcpy interface. The core is not intended for general
purpose slave DMA.
+8
View File
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_QCOM_ADM) += qcom_adm.o
obj-$(CONFIG_QCOM_BAM_DMA) += bam_dma.o
obj-$(CONFIG_QCOM_GPI_DMA) += gpi.o
obj-$(CONFIG_QCOM_HIDMA_MGMT) += hdma_mgmt.o
hdma_mgmt-objs := hidma_mgmt.o hidma_mgmt_sys.o
obj-$(CONFIG_QCOM_HIDMA) += hdma.o
hdma-objs := hidma_ll.o hidma.o hidma_dbg.o
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+958
View File
@@ -0,0 +1,958 @@
/*
* Qualcomm Technologies HIDMA DMA engine interface
*
* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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.
*/
/*
* Copyright (C) Freescale Semicondutor, Inc. 2007, 2008.
* Copyright (C) Semihalf 2009
* Copyright (C) Ilya Yanok, Emcraft Systems 2010
* Copyright (C) Alexander Popov, Promcontroller 2014
*
* Written by Piotr Ziecik <kosmo@semihalf.com>. Hardware description
* (defines, structures and comments) was taken from MPC5121 DMA driver
* written by Hongjun Chen <hong-jun.chen@freescale.com>.
*
* Approved as OSADL project by a majority of OSADL members and funded
* by OSADL membership fees in 2009; for details see www.osadl.org.
*
* 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; either version 2 of the License, or (at your option)
* any later version.
*
* 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.
*
* The full GNU General Public License is included in this distribution in the
* file called COPYING.
*/
/* Linux Foundation elects GPLv2 license only. */
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/property.h>
#include <linux/delay.h>
#include <linux/acpi.h>
#include <linux/irq.h>
#include <linux/atomic.h>
#include <linux/pm_runtime.h>
#include <linux/msi.h>
#include "../dmaengine.h"
#include "hidma.h"
/*
* Default idle time is 2 seconds. This parameter can
* be overridden by changing the following
* /sys/bus/platform/devices/QCOM8061:<xy>/power/autosuspend_delay_ms
* during kernel boot.
*/
#define HIDMA_AUTOSUSPEND_TIMEOUT 2000
#define HIDMA_ERR_INFO_SW 0xFF
#define HIDMA_ERR_CODE_UNEXPECTED_TERMINATE 0x0
#define HIDMA_NR_DEFAULT_DESC 10
#define HIDMA_MSI_INTS 11
static inline struct hidma_dev *to_hidma_dev(struct dma_device *dmadev)
{
return container_of(dmadev, struct hidma_dev, ddev);
}
static inline
struct hidma_dev *to_hidma_dev_from_lldev(struct hidma_lldev **_lldevp)
{
return container_of(_lldevp, struct hidma_dev, lldev);
}
static inline struct hidma_chan *to_hidma_chan(struct dma_chan *dmach)
{
return container_of(dmach, struct hidma_chan, chan);
}
static void hidma_free(struct hidma_dev *dmadev)
{
INIT_LIST_HEAD(&dmadev->ddev.channels);
}
static unsigned int nr_desc_prm;
module_param(nr_desc_prm, uint, 0644);
MODULE_PARM_DESC(nr_desc_prm, "number of descriptors (default: 0)");
enum hidma_cap {
HIDMA_MSI_CAP = 1,
HIDMA_IDENTITY_CAP,
};
/* process completed descriptors */
static void hidma_process_completed(struct hidma_chan *mchan)
{
struct dma_device *ddev = mchan->chan.device;
struct hidma_dev *mdma = to_hidma_dev(ddev);
struct dma_async_tx_descriptor *desc;
dma_cookie_t last_cookie;
struct hidma_desc *mdesc;
struct hidma_desc *next;
unsigned long irqflags;
struct list_head list;
INIT_LIST_HEAD(&list);
/* Get all completed descriptors */
spin_lock_irqsave(&mchan->lock, irqflags);
list_splice_tail_init(&mchan->completed, &list);
spin_unlock_irqrestore(&mchan->lock, irqflags);
/* Execute callbacks and run dependencies */
list_for_each_entry_safe(mdesc, next, &list, node) {
enum dma_status llstat;
struct dmaengine_desc_callback cb;
struct dmaengine_result result;
desc = &mdesc->desc;
last_cookie = desc->cookie;
llstat = hidma_ll_status(mdma->lldev, mdesc->tre_ch);
spin_lock_irqsave(&mchan->lock, irqflags);
if (llstat == DMA_COMPLETE) {
mchan->last_success = last_cookie;
result.result = DMA_TRANS_NOERROR;
} else {
result.result = DMA_TRANS_ABORTED;
}
dma_cookie_complete(desc);
spin_unlock_irqrestore(&mchan->lock, irqflags);
dmaengine_desc_get_callback(desc, &cb);
dma_run_dependencies(desc);
spin_lock_irqsave(&mchan->lock, irqflags);
list_move(&mdesc->node, &mchan->free);
spin_unlock_irqrestore(&mchan->lock, irqflags);
dmaengine_desc_callback_invoke(&cb, &result);
}
}
/*
* Called once for each submitted descriptor.
* PM is locked once for each descriptor that is currently
* in execution.
*/
static void hidma_callback(void *data)
{
struct hidma_desc *mdesc = data;
struct hidma_chan *mchan = to_hidma_chan(mdesc->desc.chan);
struct dma_device *ddev = mchan->chan.device;
struct hidma_dev *dmadev = to_hidma_dev(ddev);
unsigned long irqflags;
bool queued = false;
spin_lock_irqsave(&mchan->lock, irqflags);
if (mdesc->node.next) {
/* Delete from the active list, add to completed list */
list_move_tail(&mdesc->node, &mchan->completed);
queued = true;
/* calculate the next running descriptor */
mchan->running = list_first_entry(&mchan->active,
struct hidma_desc, node);
}
spin_unlock_irqrestore(&mchan->lock, irqflags);
hidma_process_completed(mchan);
if (queued) {
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
}
}
static int hidma_chan_init(struct hidma_dev *dmadev, u32 dma_sig)
{
struct hidma_chan *mchan;
struct dma_device *ddev;
mchan = devm_kzalloc(dmadev->ddev.dev, sizeof(*mchan), GFP_KERNEL);
if (!mchan)
return -ENOMEM;
ddev = &dmadev->ddev;
mchan->dma_sig = dma_sig;
mchan->dmadev = dmadev;
mchan->chan.device = ddev;
dma_cookie_init(&mchan->chan);
INIT_LIST_HEAD(&mchan->free);
INIT_LIST_HEAD(&mchan->prepared);
INIT_LIST_HEAD(&mchan->active);
INIT_LIST_HEAD(&mchan->completed);
INIT_LIST_HEAD(&mchan->queued);
spin_lock_init(&mchan->lock);
list_add_tail(&mchan->chan.device_node, &ddev->channels);
return 0;
}
static void hidma_issue_task(struct tasklet_struct *t)
{
struct hidma_dev *dmadev = from_tasklet(dmadev, t, task);
pm_runtime_get_sync(dmadev->ddev.dev);
hidma_ll_start(dmadev->lldev);
}
static void hidma_issue_pending(struct dma_chan *dmach)
{
struct hidma_chan *mchan = to_hidma_chan(dmach);
struct hidma_dev *dmadev = mchan->dmadev;
unsigned long flags;
struct hidma_desc *qdesc, *next;
int status;
spin_lock_irqsave(&mchan->lock, flags);
list_for_each_entry_safe(qdesc, next, &mchan->queued, node) {
hidma_ll_queue_request(dmadev->lldev, qdesc->tre_ch);
list_move_tail(&qdesc->node, &mchan->active);
}
if (!mchan->running) {
struct hidma_desc *desc = list_first_entry(&mchan->active,
struct hidma_desc,
node);
mchan->running = desc;
}
spin_unlock_irqrestore(&mchan->lock, flags);
/* PM will be released in hidma_callback function. */
status = pm_runtime_get(dmadev->ddev.dev);
if (status < 0)
tasklet_schedule(&dmadev->task);
else
hidma_ll_start(dmadev->lldev);
}
static inline bool hidma_txn_is_success(dma_cookie_t cookie,
dma_cookie_t last_success, dma_cookie_t last_used)
{
if (last_success <= last_used) {
if ((cookie <= last_success) || (cookie > last_used))
return true;
} else {
if ((cookie <= last_success) && (cookie > last_used))
return true;
}
return false;
}
static enum dma_status hidma_tx_status(struct dma_chan *dmach,
dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
struct hidma_chan *mchan = to_hidma_chan(dmach);
enum dma_status ret;
ret = dma_cookie_status(dmach, cookie, txstate);
if (ret == DMA_COMPLETE) {
bool is_success;
is_success = hidma_txn_is_success(cookie, mchan->last_success,
dmach->cookie);
return is_success ? ret : DMA_ERROR;
}
if (mchan->paused && (ret == DMA_IN_PROGRESS)) {
unsigned long flags;
dma_cookie_t runcookie;
spin_lock_irqsave(&mchan->lock, flags);
if (mchan->running)
runcookie = mchan->running->desc.cookie;
else
runcookie = -EINVAL;
if (runcookie == cookie)
ret = DMA_PAUSED;
spin_unlock_irqrestore(&mchan->lock, flags);
}
return ret;
}
/*
* Submit descriptor to hardware.
* Lock the PM for each descriptor we are sending.
*/
static dma_cookie_t hidma_tx_submit(struct dma_async_tx_descriptor *txd)
{
struct hidma_chan *mchan = to_hidma_chan(txd->chan);
struct hidma_dev *dmadev = mchan->dmadev;
struct hidma_desc *mdesc;
unsigned long irqflags;
dma_cookie_t cookie;
pm_runtime_get_sync(dmadev->ddev.dev);
if (!hidma_ll_isenabled(dmadev->lldev)) {
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
return -ENODEV;
}
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
mdesc = container_of(txd, struct hidma_desc, desc);
spin_lock_irqsave(&mchan->lock, irqflags);
/* Move descriptor to queued */
list_move_tail(&mdesc->node, &mchan->queued);
/* Update cookie */
cookie = dma_cookie_assign(txd);
spin_unlock_irqrestore(&mchan->lock, irqflags);
return cookie;
}
static int hidma_alloc_chan_resources(struct dma_chan *dmach)
{
struct hidma_chan *mchan = to_hidma_chan(dmach);
struct hidma_dev *dmadev = mchan->dmadev;
struct hidma_desc *mdesc, *tmp;
unsigned long irqflags;
LIST_HEAD(descs);
unsigned int i;
int rc = 0;
if (mchan->allocated)
return 0;
/* Alloc descriptors for this channel */
for (i = 0; i < dmadev->nr_descriptors; i++) {
mdesc = kzalloc_obj(struct hidma_desc, GFP_NOWAIT);
if (!mdesc) {
rc = -ENOMEM;
break;
}
dma_async_tx_descriptor_init(&mdesc->desc, dmach);
mdesc->desc.tx_submit = hidma_tx_submit;
rc = hidma_ll_request(dmadev->lldev, mchan->dma_sig,
"DMA engine", hidma_callback, mdesc,
&mdesc->tre_ch);
if (rc) {
dev_err(dmach->device->dev,
"channel alloc failed at %u\n", i);
kfree(mdesc);
break;
}
list_add_tail(&mdesc->node, &descs);
}
if (rc) {
/* return the allocated descriptors */
list_for_each_entry_safe(mdesc, tmp, &descs, node) {
hidma_ll_free(dmadev->lldev, mdesc->tre_ch);
kfree(mdesc);
}
return rc;
}
spin_lock_irqsave(&mchan->lock, irqflags);
list_splice_tail_init(&descs, &mchan->free);
mchan->allocated = true;
spin_unlock_irqrestore(&mchan->lock, irqflags);
return 1;
}
static struct dma_async_tx_descriptor *
hidma_prep_dma_memcpy(struct dma_chan *dmach, dma_addr_t dest, dma_addr_t src,
size_t len, unsigned long flags)
{
struct hidma_chan *mchan = to_hidma_chan(dmach);
struct hidma_desc *mdesc = NULL;
struct hidma_dev *mdma = mchan->dmadev;
unsigned long irqflags;
/* Get free descriptor */
spin_lock_irqsave(&mchan->lock, irqflags);
if (!list_empty(&mchan->free)) {
mdesc = list_first_entry(&mchan->free, struct hidma_desc, node);
list_del(&mdesc->node);
}
spin_unlock_irqrestore(&mchan->lock, irqflags);
if (!mdesc)
return NULL;
mdesc->desc.flags = flags;
hidma_ll_set_transfer_params(mdma->lldev, mdesc->tre_ch,
src, dest, len, flags,
HIDMA_TRE_MEMCPY);
/* Place descriptor in prepared list */
spin_lock_irqsave(&mchan->lock, irqflags);
list_add_tail(&mdesc->node, &mchan->prepared);
spin_unlock_irqrestore(&mchan->lock, irqflags);
return &mdesc->desc;
}
static struct dma_async_tx_descriptor *
hidma_prep_dma_memset(struct dma_chan *dmach, dma_addr_t dest, int value,
size_t len, unsigned long flags)
{
struct hidma_chan *mchan = to_hidma_chan(dmach);
struct hidma_desc *mdesc = NULL;
struct hidma_dev *mdma = mchan->dmadev;
unsigned long irqflags;
u64 byte_pattern, fill_pattern;
/* Get free descriptor */
spin_lock_irqsave(&mchan->lock, irqflags);
if (!list_empty(&mchan->free)) {
mdesc = list_first_entry(&mchan->free, struct hidma_desc, node);
list_del(&mdesc->node);
}
spin_unlock_irqrestore(&mchan->lock, irqflags);
if (!mdesc)
return NULL;
byte_pattern = (char)value;
fill_pattern = (byte_pattern << 56) |
(byte_pattern << 48) |
(byte_pattern << 40) |
(byte_pattern << 32) |
(byte_pattern << 24) |
(byte_pattern << 16) |
(byte_pattern << 8) |
byte_pattern;
mdesc->desc.flags = flags;
hidma_ll_set_transfer_params(mdma->lldev, mdesc->tre_ch,
fill_pattern, dest, len, flags,
HIDMA_TRE_MEMSET);
/* Place descriptor in prepared list */
spin_lock_irqsave(&mchan->lock, irqflags);
list_add_tail(&mdesc->node, &mchan->prepared);
spin_unlock_irqrestore(&mchan->lock, irqflags);
return &mdesc->desc;
}
static int hidma_terminate_channel(struct dma_chan *chan)
{
struct hidma_chan *mchan = to_hidma_chan(chan);
struct hidma_dev *dmadev = to_hidma_dev(mchan->chan.device);
struct hidma_desc *tmp, *mdesc;
unsigned long irqflags;
LIST_HEAD(list);
int rc;
pm_runtime_get_sync(dmadev->ddev.dev);
/* give completed requests a chance to finish */
hidma_process_completed(mchan);
spin_lock_irqsave(&mchan->lock, irqflags);
mchan->last_success = 0;
list_splice_init(&mchan->active, &list);
list_splice_init(&mchan->prepared, &list);
list_splice_init(&mchan->completed, &list);
list_splice_init(&mchan->queued, &list);
spin_unlock_irqrestore(&mchan->lock, irqflags);
/* this suspends the existing transfer */
rc = hidma_ll_disable(dmadev->lldev);
if (rc) {
dev_err(dmadev->ddev.dev, "channel did not pause\n");
goto out;
}
/* return all user requests */
list_for_each_entry_safe(mdesc, tmp, &list, node) {
struct dma_async_tx_descriptor *txd = &mdesc->desc;
dma_descriptor_unmap(txd);
dmaengine_desc_get_callback_invoke(txd, NULL);
dma_run_dependencies(txd);
/* move myself to free_list */
list_move(&mdesc->node, &mchan->free);
}
rc = hidma_ll_enable(dmadev->lldev);
out:
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
return rc;
}
static int hidma_terminate_all(struct dma_chan *chan)
{
struct hidma_chan *mchan = to_hidma_chan(chan);
struct hidma_dev *dmadev = to_hidma_dev(mchan->chan.device);
int rc;
rc = hidma_terminate_channel(chan);
if (rc)
return rc;
/* reinitialize the hardware */
pm_runtime_get_sync(dmadev->ddev.dev);
rc = hidma_ll_setup(dmadev->lldev);
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
return rc;
}
static void hidma_free_chan_resources(struct dma_chan *dmach)
{
struct hidma_chan *mchan = to_hidma_chan(dmach);
struct hidma_dev *mdma = mchan->dmadev;
struct hidma_desc *mdesc, *tmp;
unsigned long irqflags;
LIST_HEAD(descs);
/* terminate running transactions and free descriptors */
hidma_terminate_channel(dmach);
spin_lock_irqsave(&mchan->lock, irqflags);
/* Move data */
list_splice_tail_init(&mchan->free, &descs);
/* Free descriptors */
list_for_each_entry_safe(mdesc, tmp, &descs, node) {
hidma_ll_free(mdma->lldev, mdesc->tre_ch);
list_del(&mdesc->node);
kfree(mdesc);
}
mchan->allocated = false;
spin_unlock_irqrestore(&mchan->lock, irqflags);
}
static int hidma_pause(struct dma_chan *chan)
{
struct hidma_chan *mchan;
struct hidma_dev *dmadev;
mchan = to_hidma_chan(chan);
dmadev = to_hidma_dev(mchan->chan.device);
if (!mchan->paused) {
pm_runtime_get_sync(dmadev->ddev.dev);
if (hidma_ll_disable(dmadev->lldev))
dev_warn(dmadev->ddev.dev, "channel did not stop\n");
mchan->paused = true;
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
}
return 0;
}
static int hidma_resume(struct dma_chan *chan)
{
struct hidma_chan *mchan;
struct hidma_dev *dmadev;
int rc = 0;
mchan = to_hidma_chan(chan);
dmadev = to_hidma_dev(mchan->chan.device);
if (mchan->paused) {
pm_runtime_get_sync(dmadev->ddev.dev);
rc = hidma_ll_enable(dmadev->lldev);
if (!rc)
mchan->paused = false;
else
dev_err(dmadev->ddev.dev,
"failed to resume the channel");
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
}
return rc;
}
static irqreturn_t hidma_chirq_handler(int chirq, void *arg)
{
struct hidma_lldev *lldev = arg;
/*
* All interrupts are request driven.
* HW doesn't send an interrupt by itself.
*/
return hidma_ll_inthandler(chirq, lldev);
}
#ifdef CONFIG_GENERIC_MSI_IRQ
static irqreturn_t hidma_chirq_handler_msi(int chirq, void *arg)
{
struct hidma_lldev **lldevp = arg;
struct hidma_dev *dmadev = to_hidma_dev_from_lldev(lldevp);
return hidma_ll_inthandler_msi(chirq, *lldevp,
1 << (chirq - dmadev->msi_virqbase));
}
#endif
static ssize_t hidma_show_values(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hidma_dev *mdev = dev_get_drvdata(dev);
if (strcmp(attr->attr.name, "chid") == 0)
return sysfs_emit(buf, "%d\n", mdev->chidx);
return 0;
}
static inline void hidma_sysfs_uninit(struct hidma_dev *dev)
{
device_remove_file(dev->ddev.dev, dev->chid_attrs);
}
static struct device_attribute*
hidma_create_sysfs_entry(struct hidma_dev *dev, char *name, int mode)
{
struct device_attribute *attrs;
char *name_copy;
attrs = devm_kmalloc(dev->ddev.dev, sizeof(struct device_attribute),
GFP_KERNEL);
if (!attrs)
return NULL;
name_copy = devm_kstrdup(dev->ddev.dev, name, GFP_KERNEL);
if (!name_copy)
return NULL;
attrs->attr.name = name_copy;
attrs->attr.mode = mode;
attrs->show = hidma_show_values;
sysfs_attr_init(&attrs->attr);
return attrs;
}
static int hidma_sysfs_init(struct hidma_dev *dev)
{
dev->chid_attrs = hidma_create_sysfs_entry(dev, "chid", S_IRUGO);
if (!dev->chid_attrs)
return -ENOMEM;
return device_create_file(dev->ddev.dev, dev->chid_attrs);
}
#ifdef CONFIG_GENERIC_MSI_IRQ
static void hidma_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
{
struct device *dev = msi_desc_to_dev(desc);
struct hidma_dev *dmadev = dev_get_drvdata(dev);
if (!desc->msi_index) {
writel(msg->address_lo, dmadev->dev_evca + 0x118);
writel(msg->address_hi, dmadev->dev_evca + 0x11C);
writel(msg->data, dmadev->dev_evca + 0x120);
}
}
#endif
static void hidma_free_msis(struct hidma_dev *dmadev)
{
#ifdef CONFIG_GENERIC_MSI_IRQ
struct device *dev = dmadev->ddev.dev;
int i, virq;
for (i = 0; i < HIDMA_MSI_INTS; i++) {
virq = msi_get_virq(dev, i);
if (virq)
devm_free_irq(dev, virq, &dmadev->lldev);
}
platform_device_msi_free_irqs_all(dev);
#endif
}
static int hidma_request_msi(struct hidma_dev *dmadev,
struct platform_device *pdev)
{
#ifdef CONFIG_GENERIC_MSI_IRQ
int rc, i, virq;
rc = platform_device_msi_init_and_alloc_irqs(&pdev->dev, HIDMA_MSI_INTS,
hidma_write_msi_msg);
if (rc)
return rc;
for (i = 0; i < HIDMA_MSI_INTS; i++) {
virq = msi_get_virq(&pdev->dev, i);
rc = devm_request_irq(&pdev->dev, virq,
hidma_chirq_handler_msi,
0, "qcom-hidma-msi",
&dmadev->lldev);
if (rc)
break;
if (!i)
dmadev->msi_virqbase = virq;
}
if (rc) {
/* free allocated MSI interrupts above */
for (--i; i >= 0; i--) {
virq = msi_get_virq(&pdev->dev, i);
devm_free_irq(&pdev->dev, virq, &dmadev->lldev);
}
dev_warn(&pdev->dev,
"failed to request MSI irq, falling back to wired IRQ\n");
} else {
/* Add callback to free MSIs on teardown */
hidma_ll_setup_irq(dmadev->lldev, true);
}
return rc;
#else
return -EINVAL;
#endif
}
static bool hidma_test_capability(struct device *dev, enum hidma_cap test_cap)
{
enum hidma_cap cap;
cap = (uintptr_t) device_get_match_data(dev);
return cap ? ((cap & test_cap) > 0) : 0;
}
static int hidma_probe(struct platform_device *pdev)
{
struct hidma_dev *dmadev;
struct resource *trca_resource;
struct resource *evca_resource;
int chirq;
void __iomem *evca;
void __iomem *trca;
int rc;
bool msi;
pm_runtime_set_autosuspend_delay(&pdev->dev, HIDMA_AUTOSUSPEND_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
trca = devm_platform_get_and_ioremap_resource(pdev, 0, &trca_resource);
if (IS_ERR(trca)) {
rc = PTR_ERR(trca);
goto bailout;
}
evca = devm_platform_get_and_ioremap_resource(pdev, 1, &evca_resource);
if (IS_ERR(evca)) {
rc = PTR_ERR(evca);
goto bailout;
}
/*
* This driver only handles the channel IRQs.
* Common IRQ is handled by the management driver.
*/
chirq = platform_get_irq(pdev, 0);
if (chirq < 0) {
rc = chirq;
goto bailout;
}
dmadev = devm_kzalloc(&pdev->dev, sizeof(*dmadev), GFP_KERNEL);
if (!dmadev) {
rc = -ENOMEM;
goto bailout;
}
INIT_LIST_HEAD(&dmadev->ddev.channels);
spin_lock_init(&dmadev->lock);
dmadev->ddev.dev = &pdev->dev;
pm_runtime_get_sync(dmadev->ddev.dev);
dma_cap_set(DMA_MEMCPY, dmadev->ddev.cap_mask);
dma_cap_set(DMA_MEMSET, dmadev->ddev.cap_mask);
if (WARN_ON(!pdev->dev.dma_mask)) {
rc = -ENXIO;
goto dmafree;
}
dmadev->dev_evca = evca;
dmadev->evca_resource = evca_resource;
dmadev->dev_trca = trca;
dmadev->trca_resource = trca_resource;
dmadev->ddev.device_prep_dma_memcpy = hidma_prep_dma_memcpy;
dmadev->ddev.device_prep_dma_memset = hidma_prep_dma_memset;
dmadev->ddev.device_alloc_chan_resources = hidma_alloc_chan_resources;
dmadev->ddev.device_free_chan_resources = hidma_free_chan_resources;
dmadev->ddev.device_tx_status = hidma_tx_status;
dmadev->ddev.device_issue_pending = hidma_issue_pending;
dmadev->ddev.device_pause = hidma_pause;
dmadev->ddev.device_resume = hidma_resume;
dmadev->ddev.device_terminate_all = hidma_terminate_all;
dmadev->ddev.copy_align = 8;
/*
* Determine the MSI capability of the platform. Old HW doesn't
* support MSI.
*/
msi = hidma_test_capability(&pdev->dev, HIDMA_MSI_CAP);
device_property_read_u32(&pdev->dev, "desc-count",
&dmadev->nr_descriptors);
if (nr_desc_prm) {
dev_info(&pdev->dev, "overriding number of descriptors as %d\n",
nr_desc_prm);
dmadev->nr_descriptors = nr_desc_prm;
}
if (!dmadev->nr_descriptors)
dmadev->nr_descriptors = HIDMA_NR_DEFAULT_DESC;
if (hidma_test_capability(&pdev->dev, HIDMA_IDENTITY_CAP))
dmadev->chidx = readl(dmadev->dev_trca + 0x40);
else
dmadev->chidx = readl(dmadev->dev_trca + 0x28);
/* Set DMA mask to 64 bits. */
rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (rc) {
dev_warn(&pdev->dev, "unable to set coherent mask to 64");
goto dmafree;
}
dmadev->lldev = hidma_ll_init(dmadev->ddev.dev,
dmadev->nr_descriptors, dmadev->dev_trca,
dmadev->dev_evca, dmadev->chidx);
if (!dmadev->lldev) {
rc = -EPROBE_DEFER;
goto dmafree;
}
platform_set_drvdata(pdev, dmadev);
if (msi)
rc = hidma_request_msi(dmadev, pdev);
if (!msi || rc) {
hidma_ll_setup_irq(dmadev->lldev, false);
rc = devm_request_irq(&pdev->dev, chirq, hidma_chirq_handler,
0, "qcom-hidma", dmadev->lldev);
if (rc)
goto uninit;
}
INIT_LIST_HEAD(&dmadev->ddev.channels);
rc = hidma_chan_init(dmadev, 0);
if (rc)
goto uninit;
rc = dma_async_device_register(&dmadev->ddev);
if (rc)
goto uninit;
dmadev->irq = chirq;
tasklet_setup(&dmadev->task, hidma_issue_task);
hidma_debug_init(dmadev);
hidma_sysfs_init(dmadev);
dev_info(&pdev->dev, "HI-DMA engine driver registration complete\n");
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
return 0;
uninit:
if (msi)
hidma_free_msis(dmadev);
hidma_ll_uninit(dmadev->lldev);
dmafree:
if (dmadev)
hidma_free(dmadev);
bailout:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return rc;
}
static void hidma_shutdown(struct platform_device *pdev)
{
struct hidma_dev *dmadev = platform_get_drvdata(pdev);
dev_info(dmadev->ddev.dev, "HI-DMA engine shutdown\n");
pm_runtime_get_sync(dmadev->ddev.dev);
if (hidma_ll_disable(dmadev->lldev))
dev_warn(dmadev->ddev.dev, "channel did not stop\n");
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
}
static void hidma_remove(struct platform_device *pdev)
{
struct hidma_dev *dmadev = platform_get_drvdata(pdev);
pm_runtime_get_sync(dmadev->ddev.dev);
dma_async_device_unregister(&dmadev->ddev);
if (!dmadev->lldev->msi_support)
devm_free_irq(dmadev->ddev.dev, dmadev->irq, dmadev->lldev);
else
hidma_free_msis(dmadev);
tasklet_kill(&dmadev->task);
hidma_sysfs_uninit(dmadev);
hidma_debug_uninit(dmadev);
hidma_ll_uninit(dmadev->lldev);
hidma_free(dmadev);
dev_info(&pdev->dev, "HI-DMA engine removed\n");
pm_runtime_put_sync_suspend(&pdev->dev);
pm_runtime_disable(&pdev->dev);
}
#if IS_ENABLED(CONFIG_ACPI)
static const struct acpi_device_id hidma_acpi_ids[] = {
{"QCOM8061"},
{"QCOM8062", HIDMA_MSI_CAP},
{"QCOM8063", (HIDMA_MSI_CAP | HIDMA_IDENTITY_CAP)},
{},
};
MODULE_DEVICE_TABLE(acpi, hidma_acpi_ids);
#endif
static struct platform_driver hidma_driver = {
.probe = hidma_probe,
.remove = hidma_remove,
.shutdown = hidma_shutdown,
.driver = {
.name = "hidma",
.acpi_match_table = ACPI_PTR(hidma_acpi_ids),
},
};
module_platform_driver(hidma_driver);
MODULE_DESCRIPTION("Qualcomm Technologies HIDMA Channel support");
MODULE_LICENSE("GPL v2");
+160
View File
@@ -0,0 +1,160 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Qualcomm Technologies HIDMA data structures
*
* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
*/
#ifndef QCOM_HIDMA_H
#define QCOM_HIDMA_H
#include <linux/kfifo.h>
#include <linux/interrupt.h>
#include <linux/dmaengine.h>
#define HIDMA_TRE_SIZE 32 /* each TRE is 32 bytes */
#define HIDMA_TRE_CFG_IDX 0
#define HIDMA_TRE_LEN_IDX 1
#define HIDMA_TRE_SRC_LOW_IDX 2
#define HIDMA_TRE_SRC_HI_IDX 3
#define HIDMA_TRE_DEST_LOW_IDX 4
#define HIDMA_TRE_DEST_HI_IDX 5
enum tre_type {
HIDMA_TRE_MEMCPY = 3,
HIDMA_TRE_MEMSET = 4,
};
struct hidma_tre {
atomic_t allocated; /* if this channel is allocated */
bool queued; /* flag whether this is pending */
u16 status; /* status */
u32 idx; /* index of the tre */
u32 dma_sig; /* signature of the tre */
const char *dev_name; /* name of the device */
void (*callback)(void *data); /* requester callback */
void *data; /* Data associated with this channel*/
struct hidma_lldev *lldev; /* lldma device pointer */
u32 tre_local[HIDMA_TRE_SIZE / sizeof(u32) + 1]; /* TRE local copy */
u32 tre_index; /* the offset where this was written*/
u32 int_flags; /* interrupt flags */
u8 err_info; /* error record in this transfer */
u8 err_code; /* completion code */
};
struct hidma_lldev {
bool msi_support; /* flag indicating MSI support */
bool initialized; /* initialized flag */
u8 trch_state; /* trch_state of the device */
u8 evch_state; /* evch_state of the device */
u8 chidx; /* channel index in the core */
u32 nr_tres; /* max number of configs */
spinlock_t lock; /* reentrancy */
struct hidma_tre *trepool; /* trepool of user configs */
struct device *dev; /* device */
void __iomem *trca; /* Transfer Channel address */
void __iomem *evca; /* Event Channel address */
struct hidma_tre
**pending_tre_list; /* Pointers to pending TREs */
atomic_t pending_tre_count; /* Number of TREs pending */
void *tre_ring; /* TRE ring */
dma_addr_t tre_dma; /* TRE ring to be shared with HW */
u32 tre_ring_size; /* Byte size of the ring */
u32 tre_processed_off; /* last processed TRE */
void *evre_ring; /* EVRE ring */
dma_addr_t evre_dma; /* EVRE ring to be shared with HW */
u32 evre_ring_size; /* Byte size of the ring */
u32 evre_processed_off; /* last processed EVRE */
u32 tre_write_offset; /* TRE write location */
struct tasklet_struct task; /* task delivering notifications */
DECLARE_KFIFO_PTR(handoff_fifo,
struct hidma_tre *); /* pending TREs FIFO */
};
struct hidma_desc {
struct dma_async_tx_descriptor desc;
/* link list node for this channel*/
struct list_head node;
u32 tre_ch;
};
struct hidma_chan {
bool paused;
bool allocated;
char dbg_name[16];
u32 dma_sig;
dma_cookie_t last_success;
/*
* active descriptor on this channel
* It is used by the DMA complete notification to
* locate the descriptor that initiated the transfer.
*/
struct hidma_dev *dmadev;
struct hidma_desc *running;
struct dma_chan chan;
struct list_head free;
struct list_head prepared;
struct list_head queued;
struct list_head active;
struct list_head completed;
/* Lock for this structure */
spinlock_t lock;
};
struct hidma_dev {
int irq;
int chidx;
u32 nr_descriptors;
int msi_virqbase;
struct hidma_lldev *lldev;
void __iomem *dev_trca;
struct resource *trca_resource;
void __iomem *dev_evca;
struct resource *evca_resource;
/* used to protect the pending channel list*/
spinlock_t lock;
struct dma_device ddev;
struct dentry *debugfs;
/* sysfs entry for the channel id */
struct device_attribute *chid_attrs;
/* Task delivering issue_pending */
struct tasklet_struct task;
};
int hidma_ll_request(struct hidma_lldev *llhndl, u32 dev_id,
const char *dev_name,
void (*callback)(void *data), void *data, u32 *tre_ch);
void hidma_ll_free(struct hidma_lldev *llhndl, u32 tre_ch);
enum dma_status hidma_ll_status(struct hidma_lldev *llhndl, u32 tre_ch);
bool hidma_ll_isenabled(struct hidma_lldev *llhndl);
void hidma_ll_queue_request(struct hidma_lldev *llhndl, u32 tre_ch);
void hidma_ll_start(struct hidma_lldev *llhndl);
int hidma_ll_disable(struct hidma_lldev *lldev);
int hidma_ll_enable(struct hidma_lldev *llhndl);
void hidma_ll_set_transfer_params(struct hidma_lldev *llhndl, u32 tre_ch,
dma_addr_t src, dma_addr_t dest, u32 len, u32 flags, u32 txntype);
void hidma_ll_setup_irq(struct hidma_lldev *lldev, bool msi);
int hidma_ll_setup(struct hidma_lldev *lldev);
struct hidma_lldev *hidma_ll_init(struct device *dev, u32 max_channels,
void __iomem *trca, void __iomem *evca,
u8 chidx);
int hidma_ll_uninit(struct hidma_lldev *llhndl);
irqreturn_t hidma_ll_inthandler(int irq, void *arg);
irqreturn_t hidma_ll_inthandler_msi(int irq, void *arg, int cause);
void hidma_cleanup_pending_tre(struct hidma_lldev *llhndl, u8 err_info,
u8 err_code);
void hidma_debug_init(struct hidma_dev *dmadev);
void hidma_debug_uninit(struct hidma_dev *dmadev);
#endif
+165
View File
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm Technologies HIDMA debug file
*
* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*/
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/pm_runtime.h>
#include "hidma.h"
static void hidma_ll_chstats(struct seq_file *s, void *llhndl, u32 tre_ch)
{
struct hidma_lldev *lldev = llhndl;
struct hidma_tre *tre;
u32 length;
dma_addr_t src_start;
dma_addr_t dest_start;
u32 *tre_local;
if (tre_ch >= lldev->nr_tres) {
dev_err(lldev->dev, "invalid TRE number in chstats:%d", tre_ch);
return;
}
tre = &lldev->trepool[tre_ch];
seq_printf(s, "------Channel %d -----\n", tre_ch);
seq_printf(s, "allocated=%d\n", atomic_read(&tre->allocated));
seq_printf(s, "queued = 0x%x\n", tre->queued);
seq_printf(s, "err_info = 0x%x\n", tre->err_info);
seq_printf(s, "err_code = 0x%x\n", tre->err_code);
seq_printf(s, "status = 0x%x\n", tre->status);
seq_printf(s, "idx = 0x%x\n", tre->idx);
seq_printf(s, "dma_sig = 0x%x\n", tre->dma_sig);
seq_printf(s, "dev_name=%s\n", tre->dev_name);
seq_printf(s, "callback=%p\n", tre->callback);
seq_printf(s, "data=%p\n", tre->data);
seq_printf(s, "tre_index = 0x%x\n", tre->tre_index);
tre_local = &tre->tre_local[0];
src_start = tre_local[HIDMA_TRE_SRC_LOW_IDX];
src_start = ((u64) (tre_local[HIDMA_TRE_SRC_HI_IDX]) << 32) + src_start;
dest_start = tre_local[HIDMA_TRE_DEST_LOW_IDX];
dest_start += ((u64) (tre_local[HIDMA_TRE_DEST_HI_IDX]) << 32);
length = tre_local[HIDMA_TRE_LEN_IDX];
seq_printf(s, "src=%pap\n", &src_start);
seq_printf(s, "dest=%pap\n", &dest_start);
seq_printf(s, "length = 0x%x\n", length);
}
static void hidma_ll_devstats(struct seq_file *s, void *llhndl)
{
struct hidma_lldev *lldev = llhndl;
seq_puts(s, "------Device -----\n");
seq_printf(s, "lldev init = 0x%x\n", lldev->initialized);
seq_printf(s, "trch_state = 0x%x\n", lldev->trch_state);
seq_printf(s, "evch_state = 0x%x\n", lldev->evch_state);
seq_printf(s, "chidx = 0x%x\n", lldev->chidx);
seq_printf(s, "nr_tres = 0x%x\n", lldev->nr_tres);
seq_printf(s, "trca=%p\n", lldev->trca);
seq_printf(s, "tre_ring=%p\n", lldev->tre_ring);
seq_printf(s, "tre_ring_handle=%pap\n", &lldev->tre_dma);
seq_printf(s, "tre_ring_size = 0x%x\n", lldev->tre_ring_size);
seq_printf(s, "tre_processed_off = 0x%x\n", lldev->tre_processed_off);
seq_printf(s, "pending_tre_count=%d\n",
atomic_read(&lldev->pending_tre_count));
seq_printf(s, "evca=%p\n", lldev->evca);
seq_printf(s, "evre_ring=%p\n", lldev->evre_ring);
seq_printf(s, "evre_ring_handle=%pap\n", &lldev->evre_dma);
seq_printf(s, "evre_ring_size = 0x%x\n", lldev->evre_ring_size);
seq_printf(s, "evre_processed_off = 0x%x\n", lldev->evre_processed_off);
seq_printf(s, "tre_write_offset = 0x%x\n", lldev->tre_write_offset);
}
/*
* hidma_chan_show: display HIDMA channel statistics
*
* Display the statistics for the current HIDMA virtual channel device.
*/
static int hidma_chan_show(struct seq_file *s, void *unused)
{
struct hidma_chan *mchan = s->private;
struct hidma_desc *mdesc;
struct hidma_dev *dmadev = mchan->dmadev;
pm_runtime_get_sync(dmadev->ddev.dev);
seq_printf(s, "paused=%u\n", mchan->paused);
seq_printf(s, "dma_sig=%u\n", mchan->dma_sig);
seq_puts(s, "prepared\n");
list_for_each_entry(mdesc, &mchan->prepared, node)
hidma_ll_chstats(s, mchan->dmadev->lldev, mdesc->tre_ch);
seq_puts(s, "active\n");
list_for_each_entry(mdesc, &mchan->active, node)
hidma_ll_chstats(s, mchan->dmadev->lldev, mdesc->tre_ch);
seq_puts(s, "completed\n");
list_for_each_entry(mdesc, &mchan->completed, node)
hidma_ll_chstats(s, mchan->dmadev->lldev, mdesc->tre_ch);
hidma_ll_devstats(s, mchan->dmadev->lldev);
pm_runtime_mark_last_busy(dmadev->ddev.dev);
pm_runtime_put_autosuspend(dmadev->ddev.dev);
return 0;
}
/*
* hidma_dma_show: display HIDMA device info
*
* Display the info for the current HIDMA device.
*/
static int hidma_dma_show(struct seq_file *s, void *unused)
{
struct hidma_dev *dmadev = s->private;
resource_size_t sz;
seq_printf(s, "nr_descriptors=%d\n", dmadev->nr_descriptors);
seq_printf(s, "dev_trca=%p\n", &dmadev->dev_trca);
seq_printf(s, "dev_trca_phys=%pa\n", &dmadev->trca_resource->start);
sz = resource_size(dmadev->trca_resource);
seq_printf(s, "dev_trca_size=%pa\n", &sz);
seq_printf(s, "dev_evca=%p\n", &dmadev->dev_evca);
seq_printf(s, "dev_evca_phys=%pa\n", &dmadev->evca_resource->start);
sz = resource_size(dmadev->evca_resource);
seq_printf(s, "dev_evca_size=%pa\n", &sz);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(hidma_chan);
DEFINE_SHOW_ATTRIBUTE(hidma_dma);
void hidma_debug_uninit(struct hidma_dev *dmadev)
{
debugfs_remove_recursive(dmadev->debugfs);
}
void hidma_debug_init(struct hidma_dev *dmadev)
{
int chidx = 0;
struct list_head *position = NULL;
struct dentry *dir;
dmadev->debugfs = debugfs_create_dir(dev_name(dmadev->ddev.dev), NULL);
/* walk through the virtual channel list */
list_for_each(position, &dmadev->ddev.channels) {
struct hidma_chan *chan;
chan = list_entry(position, struct hidma_chan,
chan.device_node);
sprintf(chan->dbg_name, "chan%d", chidx);
dir = debugfs_create_dir(chan->dbg_name,
dmadev->debugfs);
debugfs_create_file("stats", S_IRUGO, dir, chan,
&hidma_chan_fops);
chidx++;
}
debugfs_create_file("stats", S_IRUGO, dmadev->debugfs, dmadev,
&hidma_dma_fops);
}
+855
View File
@@ -0,0 +1,855 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm Technologies HIDMA DMA engine low level code
*
* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*/
#include <linux/dmaengine.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/iopoll.h>
#include <linux/kfifo.h>
#include <linux/bitops.h>
#include "hidma.h"
#define HIDMA_EVRE_SIZE 16 /* each EVRE is 16 bytes */
#define HIDMA_TRCA_CTRLSTS_REG 0x000
#define HIDMA_TRCA_RING_LOW_REG 0x008
#define HIDMA_TRCA_RING_HIGH_REG 0x00C
#define HIDMA_TRCA_RING_LEN_REG 0x010
#define HIDMA_TRCA_DOORBELL_REG 0x400
#define HIDMA_EVCA_CTRLSTS_REG 0x000
#define HIDMA_EVCA_INTCTRL_REG 0x004
#define HIDMA_EVCA_RING_LOW_REG 0x008
#define HIDMA_EVCA_RING_HIGH_REG 0x00C
#define HIDMA_EVCA_RING_LEN_REG 0x010
#define HIDMA_EVCA_WRITE_PTR_REG 0x020
#define HIDMA_EVCA_DOORBELL_REG 0x400
#define HIDMA_EVCA_IRQ_STAT_REG 0x100
#define HIDMA_EVCA_IRQ_CLR_REG 0x108
#define HIDMA_EVCA_IRQ_EN_REG 0x110
#define HIDMA_EVRE_CFG_IDX 0
#define HIDMA_EVRE_ERRINFO_BIT_POS 24
#define HIDMA_EVRE_CODE_BIT_POS 28
#define HIDMA_EVRE_ERRINFO_MASK GENMASK(3, 0)
#define HIDMA_EVRE_CODE_MASK GENMASK(3, 0)
#define HIDMA_CH_CONTROL_MASK GENMASK(7, 0)
#define HIDMA_CH_STATE_MASK GENMASK(7, 0)
#define HIDMA_CH_STATE_BIT_POS 0x8
#define HIDMA_IRQ_EV_CH_EOB_IRQ_BIT_POS 0
#define HIDMA_IRQ_EV_CH_WR_RESP_BIT_POS 1
#define HIDMA_IRQ_TR_CH_TRE_RD_RSP_ER_BIT_POS 9
#define HIDMA_IRQ_TR_CH_DATA_RD_ER_BIT_POS 10
#define HIDMA_IRQ_TR_CH_DATA_WR_ER_BIT_POS 11
#define HIDMA_IRQ_TR_CH_INVALID_TRE_BIT_POS 14
#define ENABLE_IRQS (BIT(HIDMA_IRQ_EV_CH_EOB_IRQ_BIT_POS) | \
BIT(HIDMA_IRQ_EV_CH_WR_RESP_BIT_POS) | \
BIT(HIDMA_IRQ_TR_CH_TRE_RD_RSP_ER_BIT_POS) | \
BIT(HIDMA_IRQ_TR_CH_DATA_RD_ER_BIT_POS) | \
BIT(HIDMA_IRQ_TR_CH_DATA_WR_ER_BIT_POS) | \
BIT(HIDMA_IRQ_TR_CH_INVALID_TRE_BIT_POS))
#define HIDMA_INCREMENT_ITERATOR(iter, size, ring_size) \
do { \
iter += size; \
if (iter >= ring_size) \
iter -= ring_size; \
} while (0)
#define HIDMA_CH_STATE(val) \
((val >> HIDMA_CH_STATE_BIT_POS) & HIDMA_CH_STATE_MASK)
#define HIDMA_ERR_INT_MASK \
(BIT(HIDMA_IRQ_TR_CH_INVALID_TRE_BIT_POS) | \
BIT(HIDMA_IRQ_TR_CH_TRE_RD_RSP_ER_BIT_POS) | \
BIT(HIDMA_IRQ_EV_CH_WR_RESP_BIT_POS) | \
BIT(HIDMA_IRQ_TR_CH_DATA_RD_ER_BIT_POS) | \
BIT(HIDMA_IRQ_TR_CH_DATA_WR_ER_BIT_POS))
enum ch_command {
HIDMA_CH_DISABLE = 0,
HIDMA_CH_ENABLE = 1,
HIDMA_CH_SUSPEND = 2,
HIDMA_CH_RESET = 9,
};
enum ch_state {
HIDMA_CH_DISABLED = 0,
HIDMA_CH_ENABLED = 1,
HIDMA_CH_RUNNING = 2,
HIDMA_CH_SUSPENDED = 3,
HIDMA_CH_STOPPED = 4,
};
enum err_code {
HIDMA_EVRE_STATUS_COMPLETE = 1,
HIDMA_EVRE_STATUS_ERROR = 4,
};
static int hidma_is_chan_enabled(int state)
{
switch (state) {
case HIDMA_CH_ENABLED:
case HIDMA_CH_RUNNING:
return true;
default:
return false;
}
}
void hidma_ll_free(struct hidma_lldev *lldev, u32 tre_ch)
{
struct hidma_tre *tre;
if (tre_ch >= lldev->nr_tres) {
dev_err(lldev->dev, "invalid TRE number in free:%d", tre_ch);
return;
}
tre = &lldev->trepool[tre_ch];
if (atomic_read(&tre->allocated) != true) {
dev_err(lldev->dev, "trying to free an unused TRE:%d", tre_ch);
return;
}
atomic_set(&tre->allocated, 0);
}
int hidma_ll_request(struct hidma_lldev *lldev, u32 sig, const char *dev_name,
void (*callback)(void *data), void *data, u32 *tre_ch)
{
unsigned int i;
struct hidma_tre *tre;
u32 *tre_local;
if (!tre_ch || !lldev)
return -EINVAL;
/* need to have at least one empty spot in the queue */
for (i = 0; i < lldev->nr_tres - 1; i++) {
if (atomic_add_unless(&lldev->trepool[i].allocated, 1, 1))
break;
}
if (i == (lldev->nr_tres - 1))
return -ENOMEM;
tre = &lldev->trepool[i];
tre->dma_sig = sig;
tre->dev_name = dev_name;
tre->callback = callback;
tre->data = data;
tre->idx = i;
tre->status = 0;
tre->queued = 0;
tre->err_code = 0;
tre->err_info = 0;
tre->lldev = lldev;
tre_local = &tre->tre_local[0];
tre_local[HIDMA_TRE_CFG_IDX] = (lldev->chidx & 0xFF) << 8;
tre_local[HIDMA_TRE_CFG_IDX] |= BIT(16); /* set IEOB */
*tre_ch = i;
if (callback)
callback(data);
return 0;
}
/*
* Multiple TREs may be queued and waiting in the pending queue.
*/
static void hidma_ll_tre_complete(struct tasklet_struct *t)
{
struct hidma_lldev *lldev = from_tasklet(lldev, t, task);
struct hidma_tre *tre;
while (kfifo_out(&lldev->handoff_fifo, &tre, 1)) {
/* call the user if it has been read by the hardware */
if (tre->callback)
tre->callback(tre->data);
}
}
static int hidma_post_completed(struct hidma_lldev *lldev, u8 err_info,
u8 err_code)
{
struct hidma_tre *tre;
unsigned long flags;
u32 tre_iterator;
spin_lock_irqsave(&lldev->lock, flags);
tre_iterator = lldev->tre_processed_off;
tre = lldev->pending_tre_list[tre_iterator / HIDMA_TRE_SIZE];
if (!tre) {
spin_unlock_irqrestore(&lldev->lock, flags);
dev_warn(lldev->dev, "tre_index [%d] and tre out of sync\n",
tre_iterator / HIDMA_TRE_SIZE);
return -EINVAL;
}
lldev->pending_tre_list[tre->tre_index] = NULL;
/*
* Keep track of pending TREs that SW is expecting to receive
* from HW. We got one now. Decrement our counter.
*/
if (atomic_dec_return(&lldev->pending_tre_count) < 0) {
dev_warn(lldev->dev, "tre count mismatch on completion");
atomic_set(&lldev->pending_tre_count, 0);
}
HIDMA_INCREMENT_ITERATOR(tre_iterator, HIDMA_TRE_SIZE,
lldev->tre_ring_size);
lldev->tre_processed_off = tre_iterator;
spin_unlock_irqrestore(&lldev->lock, flags);
tre->err_info = err_info;
tre->err_code = err_code;
tre->queued = 0;
kfifo_put(&lldev->handoff_fifo, tre);
tasklet_schedule(&lldev->task);
return 0;
}
/*
* Called to handle the interrupt for the channel.
* Return a positive number if TRE or EVRE were consumed on this run.
* Return a positive number if there are pending TREs or EVREs.
* Return 0 if there is nothing to consume or no pending TREs/EVREs found.
*/
static int hidma_handle_tre_completion(struct hidma_lldev *lldev)
{
u32 evre_ring_size = lldev->evre_ring_size;
u32 err_info, err_code, evre_write_off;
u32 evre_iterator;
u32 num_completed = 0;
evre_write_off = readl_relaxed(lldev->evca + HIDMA_EVCA_WRITE_PTR_REG);
evre_iterator = lldev->evre_processed_off;
if ((evre_write_off > evre_ring_size) ||
(evre_write_off % HIDMA_EVRE_SIZE)) {
dev_err(lldev->dev, "HW reports invalid EVRE write offset\n");
return 0;
}
/*
* By the time control reaches here the number of EVREs and TREs
* may not match. Only consume the ones that hardware told us.
*/
while ((evre_iterator != evre_write_off)) {
u32 *current_evre = lldev->evre_ring + evre_iterator;
u32 cfg;
cfg = current_evre[HIDMA_EVRE_CFG_IDX];
err_info = cfg >> HIDMA_EVRE_ERRINFO_BIT_POS;
err_info &= HIDMA_EVRE_ERRINFO_MASK;
err_code =
(cfg >> HIDMA_EVRE_CODE_BIT_POS) & HIDMA_EVRE_CODE_MASK;
if (hidma_post_completed(lldev, err_info, err_code))
break;
HIDMA_INCREMENT_ITERATOR(evre_iterator, HIDMA_EVRE_SIZE,
evre_ring_size);
/*
* Read the new event descriptor written by the HW.
* As we are processing the delivered events, other events
* get queued to the SW for processing.
*/
evre_write_off =
readl_relaxed(lldev->evca + HIDMA_EVCA_WRITE_PTR_REG);
num_completed++;
/*
* An error interrupt might have arrived while we are processing
* the completed interrupt.
*/
if (!hidma_ll_isenabled(lldev))
break;
}
if (num_completed) {
u32 evre_read_off = (lldev->evre_processed_off +
HIDMA_EVRE_SIZE * num_completed);
evre_read_off = evre_read_off % evre_ring_size;
writel(evre_read_off, lldev->evca + HIDMA_EVCA_DOORBELL_REG);
/* record the last processed tre offset */
lldev->evre_processed_off = evre_read_off;
}
return num_completed;
}
void hidma_cleanup_pending_tre(struct hidma_lldev *lldev, u8 err_info,
u8 err_code)
{
while (atomic_read(&lldev->pending_tre_count)) {
if (hidma_post_completed(lldev, err_info, err_code))
break;
}
}
static int hidma_ll_reset(struct hidma_lldev *lldev)
{
u32 val;
int ret;
val = readl(lldev->trca + HIDMA_TRCA_CTRLSTS_REG);
val &= ~(HIDMA_CH_CONTROL_MASK << 16);
val |= HIDMA_CH_RESET << 16;
writel(val, lldev->trca + HIDMA_TRCA_CTRLSTS_REG);
/*
* Delay 10ms after reset to allow DMA logic to quiesce.
* Do a polled read up to 1ms and 10ms maximum.
*/
ret = readl_poll_timeout(lldev->trca + HIDMA_TRCA_CTRLSTS_REG, val,
HIDMA_CH_STATE(val) == HIDMA_CH_DISABLED,
1000, 10000);
if (ret) {
dev_err(lldev->dev, "transfer channel did not reset\n");
return ret;
}
val = readl(lldev->evca + HIDMA_EVCA_CTRLSTS_REG);
val &= ~(HIDMA_CH_CONTROL_MASK << 16);
val |= HIDMA_CH_RESET << 16;
writel(val, lldev->evca + HIDMA_EVCA_CTRLSTS_REG);
/*
* Delay 10ms after reset to allow DMA logic to quiesce.
* Do a polled read up to 1ms and 10ms maximum.
*/
ret = readl_poll_timeout(lldev->evca + HIDMA_EVCA_CTRLSTS_REG, val,
HIDMA_CH_STATE(val) == HIDMA_CH_DISABLED,
1000, 10000);
if (ret)
return ret;
lldev->trch_state = HIDMA_CH_DISABLED;
lldev->evch_state = HIDMA_CH_DISABLED;
return 0;
}
/*
* The interrupt handler for HIDMA will try to consume as many pending
* EVRE from the event queue as possible. Each EVRE has an associated
* TRE that holds the user interface parameters. EVRE reports the
* result of the transaction. Hardware guarantees ordering between EVREs
* and TREs. We use last processed offset to figure out which TRE is
* associated with which EVRE. If two TREs are consumed by HW, the EVREs
* are in order in the event ring.
*
* This handler will do a one pass for consuming EVREs. Other EVREs may
* be delivered while we are working. It will try to consume incoming
* EVREs one more time and return.
*
* For unprocessed EVREs, hardware will trigger another interrupt until
* all the interrupt bits are cleared.
*
* Hardware guarantees that by the time interrupt is observed, all data
* transactions in flight are delivered to their respective places and
* are visible to the CPU.
*
* On demand paging for IOMMU is only supported for PCIe via PRI
* (Page Request Interface) not for HIDMA. All other hardware instances
* including HIDMA work on pinned DMA addresses.
*
* HIDMA is not aware of IOMMU presence since it follows the DMA API. All
* IOMMU latency will be built into the data movement time. By the time
* interrupt happens, IOMMU lookups + data movement has already taken place.
*
* While the first read in a typical PCI endpoint ISR flushes all outstanding
* requests traditionally to the destination, this concept does not apply
* here for this HW.
*/
static void hidma_ll_int_handler_internal(struct hidma_lldev *lldev, int cause)
{
unsigned long irqflags;
if (cause & HIDMA_ERR_INT_MASK) {
dev_err(lldev->dev, "error 0x%x, disabling...\n",
cause);
/* Clear out pending interrupts */
writel(cause, lldev->evca + HIDMA_EVCA_IRQ_CLR_REG);
/* No further submissions. */
hidma_ll_disable(lldev);
/* Driver completes the txn and intimates the client.*/
hidma_cleanup_pending_tre(lldev, 0xFF,
HIDMA_EVRE_STATUS_ERROR);
return;
}
spin_lock_irqsave(&lldev->lock, irqflags);
writel_relaxed(cause, lldev->evca + HIDMA_EVCA_IRQ_CLR_REG);
spin_unlock_irqrestore(&lldev->lock, irqflags);
/*
* Fine tuned for this HW...
*
* This ISR has been designed for this particular hardware. Relaxed
* read and write accessors are used for performance reasons due to
* interrupt delivery guarantees. Do not copy this code blindly and
* expect that to work.
*
* Try to consume as many EVREs as possible.
*/
hidma_handle_tre_completion(lldev);
}
irqreturn_t hidma_ll_inthandler(int chirq, void *arg)
{
struct hidma_lldev *lldev = arg;
u32 status;
u32 enable;
u32 cause;
status = readl_relaxed(lldev->evca + HIDMA_EVCA_IRQ_STAT_REG);
enable = readl_relaxed(lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
cause = status & enable;
while (cause) {
hidma_ll_int_handler_internal(lldev, cause);
/*
* Another interrupt might have arrived while we are
* processing this one. Read the new cause.
*/
status = readl_relaxed(lldev->evca + HIDMA_EVCA_IRQ_STAT_REG);
enable = readl_relaxed(lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
cause = status & enable;
}
return IRQ_HANDLED;
}
irqreturn_t hidma_ll_inthandler_msi(int chirq, void *arg, int cause)
{
struct hidma_lldev *lldev = arg;
hidma_ll_int_handler_internal(lldev, cause);
return IRQ_HANDLED;
}
int hidma_ll_enable(struct hidma_lldev *lldev)
{
u32 val;
int ret;
val = readl(lldev->evca + HIDMA_EVCA_CTRLSTS_REG);
val &= ~(HIDMA_CH_CONTROL_MASK << 16);
val |= HIDMA_CH_ENABLE << 16;
writel(val, lldev->evca + HIDMA_EVCA_CTRLSTS_REG);
ret = readl_poll_timeout(lldev->evca + HIDMA_EVCA_CTRLSTS_REG, val,
hidma_is_chan_enabled(HIDMA_CH_STATE(val)),
1000, 10000);
if (ret) {
dev_err(lldev->dev, "event channel did not get enabled\n");
return ret;
}
val = readl(lldev->trca + HIDMA_TRCA_CTRLSTS_REG);
val &= ~(HIDMA_CH_CONTROL_MASK << 16);
val |= HIDMA_CH_ENABLE << 16;
writel(val, lldev->trca + HIDMA_TRCA_CTRLSTS_REG);
ret = readl_poll_timeout(lldev->trca + HIDMA_TRCA_CTRLSTS_REG, val,
hidma_is_chan_enabled(HIDMA_CH_STATE(val)),
1000, 10000);
if (ret) {
dev_err(lldev->dev, "transfer channel did not get enabled\n");
return ret;
}
lldev->trch_state = HIDMA_CH_ENABLED;
lldev->evch_state = HIDMA_CH_ENABLED;
/* enable irqs */
writel(ENABLE_IRQS, lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
return 0;
}
void hidma_ll_start(struct hidma_lldev *lldev)
{
unsigned long irqflags;
spin_lock_irqsave(&lldev->lock, irqflags);
writel(lldev->tre_write_offset, lldev->trca + HIDMA_TRCA_DOORBELL_REG);
spin_unlock_irqrestore(&lldev->lock, irqflags);
}
bool hidma_ll_isenabled(struct hidma_lldev *lldev)
{
u32 val;
val = readl(lldev->trca + HIDMA_TRCA_CTRLSTS_REG);
lldev->trch_state = HIDMA_CH_STATE(val);
val = readl(lldev->evca + HIDMA_EVCA_CTRLSTS_REG);
lldev->evch_state = HIDMA_CH_STATE(val);
/* both channels have to be enabled before calling this function */
if (hidma_is_chan_enabled(lldev->trch_state) &&
hidma_is_chan_enabled(lldev->evch_state))
return true;
return false;
}
void hidma_ll_queue_request(struct hidma_lldev *lldev, u32 tre_ch)
{
struct hidma_tre *tre;
unsigned long flags;
tre = &lldev->trepool[tre_ch];
/* copy the TRE into its location in the TRE ring */
spin_lock_irqsave(&lldev->lock, flags);
tre->tre_index = lldev->tre_write_offset / HIDMA_TRE_SIZE;
lldev->pending_tre_list[tre->tre_index] = tre;
memcpy(lldev->tre_ring + lldev->tre_write_offset,
&tre->tre_local[0], HIDMA_TRE_SIZE);
tre->err_code = 0;
tre->err_info = 0;
tre->queued = 1;
atomic_inc(&lldev->pending_tre_count);
lldev->tre_write_offset = (lldev->tre_write_offset + HIDMA_TRE_SIZE)
% lldev->tre_ring_size;
spin_unlock_irqrestore(&lldev->lock, flags);
}
/*
* Note that even though we stop this channel if there is a pending transaction
* in flight it will complete and follow the callback. This request will
* prevent further requests to be made.
*/
int hidma_ll_disable(struct hidma_lldev *lldev)
{
u32 val;
int ret;
/* The channel needs to be in working state */
if (!hidma_ll_isenabled(lldev))
return 0;
val = readl(lldev->trca + HIDMA_TRCA_CTRLSTS_REG);
val &= ~(HIDMA_CH_CONTROL_MASK << 16);
val |= HIDMA_CH_SUSPEND << 16;
writel(val, lldev->trca + HIDMA_TRCA_CTRLSTS_REG);
/*
* Start the wait right after the suspend is confirmed.
* Do a polled read up to 1ms and 10ms maximum.
*/
ret = readl_poll_timeout(lldev->trca + HIDMA_TRCA_CTRLSTS_REG, val,
HIDMA_CH_STATE(val) == HIDMA_CH_SUSPENDED,
1000, 10000);
if (ret)
return ret;
val = readl(lldev->evca + HIDMA_EVCA_CTRLSTS_REG);
val &= ~(HIDMA_CH_CONTROL_MASK << 16);
val |= HIDMA_CH_SUSPEND << 16;
writel(val, lldev->evca + HIDMA_EVCA_CTRLSTS_REG);
/*
* Start the wait right after the suspend is confirmed
* Delay up to 10ms after reset to allow DMA logic to quiesce.
*/
ret = readl_poll_timeout(lldev->evca + HIDMA_EVCA_CTRLSTS_REG, val,
HIDMA_CH_STATE(val) == HIDMA_CH_SUSPENDED,
1000, 10000);
if (ret)
return ret;
lldev->trch_state = HIDMA_CH_SUSPENDED;
lldev->evch_state = HIDMA_CH_SUSPENDED;
/* disable interrupts */
writel(0, lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
return 0;
}
void hidma_ll_set_transfer_params(struct hidma_lldev *lldev, u32 tre_ch,
dma_addr_t src, dma_addr_t dest, u32 len,
u32 flags, u32 txntype)
{
struct hidma_tre *tre;
u32 *tre_local;
if (tre_ch >= lldev->nr_tres) {
dev_err(lldev->dev, "invalid TRE number in transfer params:%d",
tre_ch);
return;
}
tre = &lldev->trepool[tre_ch];
if (atomic_read(&tre->allocated) != true) {
dev_err(lldev->dev, "trying to set params on an unused TRE:%d",
tre_ch);
return;
}
tre_local = &tre->tre_local[0];
tre_local[HIDMA_TRE_CFG_IDX] &= ~GENMASK(7, 0);
tre_local[HIDMA_TRE_CFG_IDX] |= txntype;
tre_local[HIDMA_TRE_LEN_IDX] = len;
tre_local[HIDMA_TRE_SRC_LOW_IDX] = lower_32_bits(src);
tre_local[HIDMA_TRE_SRC_HI_IDX] = upper_32_bits(src);
tre_local[HIDMA_TRE_DEST_LOW_IDX] = lower_32_bits(dest);
tre_local[HIDMA_TRE_DEST_HI_IDX] = upper_32_bits(dest);
tre->int_flags = flags;
}
/*
* Called during initialization and after an error condition
* to restore hardware state.
*/
int hidma_ll_setup(struct hidma_lldev *lldev)
{
int rc;
u64 addr;
u32 val;
u32 nr_tres = lldev->nr_tres;
atomic_set(&lldev->pending_tre_count, 0);
lldev->tre_processed_off = 0;
lldev->evre_processed_off = 0;
lldev->tre_write_offset = 0;
/* disable interrupts */
writel(0, lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
/* clear all pending interrupts */
val = readl(lldev->evca + HIDMA_EVCA_IRQ_STAT_REG);
writel(val, lldev->evca + HIDMA_EVCA_IRQ_CLR_REG);
rc = hidma_ll_reset(lldev);
if (rc)
return rc;
/*
* Clear all pending interrupts again.
* Otherwise, we observe reset complete interrupts.
*/
val = readl(lldev->evca + HIDMA_EVCA_IRQ_STAT_REG);
writel(val, lldev->evca + HIDMA_EVCA_IRQ_CLR_REG);
/* disable interrupts again after reset */
writel(0, lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
addr = lldev->tre_dma;
writel(lower_32_bits(addr), lldev->trca + HIDMA_TRCA_RING_LOW_REG);
writel(upper_32_bits(addr), lldev->trca + HIDMA_TRCA_RING_HIGH_REG);
writel(lldev->tre_ring_size, lldev->trca + HIDMA_TRCA_RING_LEN_REG);
addr = lldev->evre_dma;
writel(lower_32_bits(addr), lldev->evca + HIDMA_EVCA_RING_LOW_REG);
writel(upper_32_bits(addr), lldev->evca + HIDMA_EVCA_RING_HIGH_REG);
writel(HIDMA_EVRE_SIZE * nr_tres,
lldev->evca + HIDMA_EVCA_RING_LEN_REG);
/* configure interrupts */
hidma_ll_setup_irq(lldev, lldev->msi_support);
rc = hidma_ll_enable(lldev);
if (rc)
return rc;
return rc;
}
void hidma_ll_setup_irq(struct hidma_lldev *lldev, bool msi)
{
u32 val;
lldev->msi_support = msi;
/* disable interrupts again after reset */
writel(0, lldev->evca + HIDMA_EVCA_IRQ_CLR_REG);
writel(0, lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
/* support IRQ by default */
val = readl(lldev->evca + HIDMA_EVCA_INTCTRL_REG);
val &= ~0xF;
if (!lldev->msi_support)
val = val | 0x1;
writel(val, lldev->evca + HIDMA_EVCA_INTCTRL_REG);
/* clear all pending interrupts and enable them */
writel(ENABLE_IRQS, lldev->evca + HIDMA_EVCA_IRQ_CLR_REG);
writel(ENABLE_IRQS, lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
}
struct hidma_lldev *hidma_ll_init(struct device *dev, u32 nr_tres,
void __iomem *trca, void __iomem *evca,
u8 chidx)
{
u32 required_bytes;
struct hidma_lldev *lldev;
int rc;
size_t sz;
if (!trca || !evca || !dev || !nr_tres)
return NULL;
/* need at least four TREs */
if (nr_tres < 4)
return NULL;
/* need an extra space */
nr_tres += 1;
lldev = devm_kzalloc(dev, sizeof(struct hidma_lldev), GFP_KERNEL);
if (!lldev)
return NULL;
lldev->evca = evca;
lldev->trca = trca;
lldev->dev = dev;
sz = sizeof(struct hidma_tre);
lldev->trepool = devm_kcalloc(lldev->dev, nr_tres, sz, GFP_KERNEL);
if (!lldev->trepool)
return NULL;
required_bytes = sizeof(lldev->pending_tre_list[0]);
lldev->pending_tre_list = devm_kcalloc(dev, nr_tres, required_bytes,
GFP_KERNEL);
if (!lldev->pending_tre_list)
return NULL;
sz = (HIDMA_TRE_SIZE + 1) * nr_tres;
lldev->tre_ring = dmam_alloc_coherent(dev, sz, &lldev->tre_dma,
GFP_KERNEL);
if (!lldev->tre_ring)
return NULL;
lldev->tre_ring_size = HIDMA_TRE_SIZE * nr_tres;
lldev->nr_tres = nr_tres;
/* the TRE ring has to be TRE_SIZE aligned */
if (!IS_ALIGNED(lldev->tre_dma, HIDMA_TRE_SIZE)) {
u8 tre_ring_shift;
tre_ring_shift = lldev->tre_dma % HIDMA_TRE_SIZE;
tre_ring_shift = HIDMA_TRE_SIZE - tre_ring_shift;
lldev->tre_dma += tre_ring_shift;
lldev->tre_ring += tre_ring_shift;
}
sz = (HIDMA_EVRE_SIZE + 1) * nr_tres;
lldev->evre_ring = dmam_alloc_coherent(dev, sz, &lldev->evre_dma,
GFP_KERNEL);
if (!lldev->evre_ring)
return NULL;
lldev->evre_ring_size = HIDMA_EVRE_SIZE * nr_tres;
/* the EVRE ring has to be EVRE_SIZE aligned */
if (!IS_ALIGNED(lldev->evre_dma, HIDMA_EVRE_SIZE)) {
u8 evre_ring_shift;
evre_ring_shift = lldev->evre_dma % HIDMA_EVRE_SIZE;
evre_ring_shift = HIDMA_EVRE_SIZE - evre_ring_shift;
lldev->evre_dma += evre_ring_shift;
lldev->evre_ring += evre_ring_shift;
}
lldev->nr_tres = nr_tres;
lldev->chidx = chidx;
sz = nr_tres * sizeof(struct hidma_tre *);
rc = kfifo_alloc(&lldev->handoff_fifo, sz, GFP_KERNEL);
if (rc)
return NULL;
rc = hidma_ll_setup(lldev);
if (rc)
return NULL;
spin_lock_init(&lldev->lock);
tasklet_setup(&lldev->task, hidma_ll_tre_complete);
lldev->initialized = 1;
writel(ENABLE_IRQS, lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
return lldev;
}
int hidma_ll_uninit(struct hidma_lldev *lldev)
{
u32 required_bytes;
int rc = 0;
u32 val;
if (!lldev)
return -ENODEV;
if (!lldev->initialized)
return 0;
lldev->initialized = 0;
required_bytes = sizeof(struct hidma_tre) * lldev->nr_tres;
tasklet_kill(&lldev->task);
memset(lldev->trepool, 0, required_bytes);
lldev->trepool = NULL;
atomic_set(&lldev->pending_tre_count, 0);
lldev->tre_write_offset = 0;
rc = hidma_ll_reset(lldev);
/*
* Clear all pending interrupts again.
* Otherwise, we observe reset complete interrupts.
*/
val = readl(lldev->evca + HIDMA_EVCA_IRQ_STAT_REG);
writel(val, lldev->evca + HIDMA_EVCA_IRQ_CLR_REG);
writel(0, lldev->evca + HIDMA_EVCA_IRQ_EN_REG);
return rc;
}
enum dma_status hidma_ll_status(struct hidma_lldev *lldev, u32 tre_ch)
{
enum dma_status ret = DMA_ERROR;
struct hidma_tre *tre;
unsigned long flags;
u8 err_code;
spin_lock_irqsave(&lldev->lock, flags);
tre = &lldev->trepool[tre_ch];
err_code = tre->err_code;
if (err_code & HIDMA_EVRE_STATUS_COMPLETE)
ret = DMA_COMPLETE;
else if (err_code & HIDMA_EVRE_STATUS_ERROR)
ret = DMA_ERROR;
else
ret = DMA_IN_PROGRESS;
spin_unlock_irqrestore(&lldev->lock, flags);
return ret;
}
+335
View File
@@ -0,0 +1,335 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm Technologies HIDMA DMA engine Management interface
*
* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*/
#include <linux/dmaengine.h>
#include <linux/acpi.h>
#include <linux/property.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/bitops.h>
#include <linux/dma-mapping.h>
#include "hidma_mgmt.h"
#define HIDMA_QOS_N_OFFSET 0x700
#define HIDMA_CFG_OFFSET 0x400
#define HIDMA_MAX_BUS_REQ_LEN_OFFSET 0x41C
#define HIDMA_MAX_XACTIONS_OFFSET 0x420
#define HIDMA_HW_VERSION_OFFSET 0x424
#define HIDMA_CHRESET_TIMEOUT_OFFSET 0x418
#define HIDMA_MAX_WR_XACTIONS_MASK GENMASK(4, 0)
#define HIDMA_MAX_RD_XACTIONS_MASK GENMASK(4, 0)
#define HIDMA_WEIGHT_MASK GENMASK(6, 0)
#define HIDMA_MAX_BUS_REQ_LEN_MASK GENMASK(15, 0)
#define HIDMA_CHRESET_TIMEOUT_MASK GENMASK(19, 0)
#define HIDMA_MAX_WR_XACTIONS_BIT_POS 16
#define HIDMA_MAX_BUS_WR_REQ_BIT_POS 16
#define HIDMA_WRR_BIT_POS 8
#define HIDMA_PRIORITY_BIT_POS 15
#define HIDMA_AUTOSUSPEND_TIMEOUT 2000
#define HIDMA_MAX_CHANNEL_WEIGHT 15
static unsigned int max_write_request;
module_param(max_write_request, uint, 0644);
MODULE_PARM_DESC(max_write_request,
"maximum write burst (default: ACPI/DT value)");
static unsigned int max_read_request;
module_param(max_read_request, uint, 0644);
MODULE_PARM_DESC(max_read_request,
"maximum read burst (default: ACPI/DT value)");
static unsigned int max_wr_xactions;
module_param(max_wr_xactions, uint, 0644);
MODULE_PARM_DESC(max_wr_xactions,
"maximum number of write transactions (default: ACPI/DT value)");
static unsigned int max_rd_xactions;
module_param(max_rd_xactions, uint, 0644);
MODULE_PARM_DESC(max_rd_xactions,
"maximum number of read transactions (default: ACPI/DT value)");
int hidma_mgmt_setup(struct hidma_mgmt_dev *mgmtdev)
{
unsigned int i;
u32 val;
if (!is_power_of_2(mgmtdev->max_write_request) ||
(mgmtdev->max_write_request < 128) ||
(mgmtdev->max_write_request > 1024)) {
dev_err(&mgmtdev->pdev->dev, "invalid write request %d\n",
mgmtdev->max_write_request);
return -EINVAL;
}
if (!is_power_of_2(mgmtdev->max_read_request) ||
(mgmtdev->max_read_request < 128) ||
(mgmtdev->max_read_request > 1024)) {
dev_err(&mgmtdev->pdev->dev, "invalid read request %d\n",
mgmtdev->max_read_request);
return -EINVAL;
}
if (mgmtdev->max_wr_xactions > HIDMA_MAX_WR_XACTIONS_MASK) {
dev_err(&mgmtdev->pdev->dev,
"max_wr_xactions cannot be bigger than %ld\n",
HIDMA_MAX_WR_XACTIONS_MASK);
return -EINVAL;
}
if (mgmtdev->max_rd_xactions > HIDMA_MAX_RD_XACTIONS_MASK) {
dev_err(&mgmtdev->pdev->dev,
"max_rd_xactions cannot be bigger than %ld\n",
HIDMA_MAX_RD_XACTIONS_MASK);
return -EINVAL;
}
for (i = 0; i < mgmtdev->dma_channels; i++) {
if (mgmtdev->priority[i] > 1) {
dev_err(&mgmtdev->pdev->dev,
"priority can be 0 or 1\n");
return -EINVAL;
}
if (mgmtdev->weight[i] > HIDMA_MAX_CHANNEL_WEIGHT) {
dev_err(&mgmtdev->pdev->dev,
"max value of weight can be %d.\n",
HIDMA_MAX_CHANNEL_WEIGHT);
return -EINVAL;
}
/* weight needs to be at least one */
if (mgmtdev->weight[i] == 0)
mgmtdev->weight[i] = 1;
}
pm_runtime_get_sync(&mgmtdev->pdev->dev);
val = readl(mgmtdev->virtaddr + HIDMA_MAX_BUS_REQ_LEN_OFFSET);
val &= ~(HIDMA_MAX_BUS_REQ_LEN_MASK << HIDMA_MAX_BUS_WR_REQ_BIT_POS);
val |= mgmtdev->max_write_request << HIDMA_MAX_BUS_WR_REQ_BIT_POS;
val &= ~HIDMA_MAX_BUS_REQ_LEN_MASK;
val |= mgmtdev->max_read_request;
writel(val, mgmtdev->virtaddr + HIDMA_MAX_BUS_REQ_LEN_OFFSET);
val = readl(mgmtdev->virtaddr + HIDMA_MAX_XACTIONS_OFFSET);
val &= ~(HIDMA_MAX_WR_XACTIONS_MASK << HIDMA_MAX_WR_XACTIONS_BIT_POS);
val |= mgmtdev->max_wr_xactions << HIDMA_MAX_WR_XACTIONS_BIT_POS;
val &= ~HIDMA_MAX_RD_XACTIONS_MASK;
val |= mgmtdev->max_rd_xactions;
writel(val, mgmtdev->virtaddr + HIDMA_MAX_XACTIONS_OFFSET);
mgmtdev->hw_version =
readl(mgmtdev->virtaddr + HIDMA_HW_VERSION_OFFSET);
mgmtdev->hw_version_major = (mgmtdev->hw_version >> 28) & 0xF;
mgmtdev->hw_version_minor = (mgmtdev->hw_version >> 16) & 0xF;
for (i = 0; i < mgmtdev->dma_channels; i++) {
u32 weight = mgmtdev->weight[i];
u32 priority = mgmtdev->priority[i];
val = readl(mgmtdev->virtaddr + HIDMA_QOS_N_OFFSET + (4 * i));
val &= ~(1 << HIDMA_PRIORITY_BIT_POS);
val |= (priority & 0x1) << HIDMA_PRIORITY_BIT_POS;
val &= ~(HIDMA_WEIGHT_MASK << HIDMA_WRR_BIT_POS);
val |= (weight & HIDMA_WEIGHT_MASK) << HIDMA_WRR_BIT_POS;
writel(val, mgmtdev->virtaddr + HIDMA_QOS_N_OFFSET + (4 * i));
}
val = readl(mgmtdev->virtaddr + HIDMA_CHRESET_TIMEOUT_OFFSET);
val &= ~HIDMA_CHRESET_TIMEOUT_MASK;
val |= mgmtdev->chreset_timeout_cycles & HIDMA_CHRESET_TIMEOUT_MASK;
writel(val, mgmtdev->virtaddr + HIDMA_CHRESET_TIMEOUT_OFFSET);
pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
return 0;
}
EXPORT_SYMBOL_GPL(hidma_mgmt_setup);
static int hidma_mgmt_probe(struct platform_device *pdev)
{
struct hidma_mgmt_dev *mgmtdev;
struct resource *res;
void __iomem *virtaddr;
int irq;
int rc;
u32 val;
pm_runtime_set_autosuspend_delay(&pdev->dev, HIDMA_AUTOSUSPEND_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
virtaddr = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(virtaddr)) {
rc = PTR_ERR(virtaddr);
goto out;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
rc = irq;
goto out;
}
mgmtdev = devm_kzalloc(&pdev->dev, sizeof(*mgmtdev), GFP_KERNEL);
if (!mgmtdev) {
rc = -ENOMEM;
goto out;
}
mgmtdev->pdev = pdev;
mgmtdev->addrsize = resource_size(res);
mgmtdev->virtaddr = virtaddr;
rc = device_property_read_u32(&pdev->dev, "dma-channels",
&mgmtdev->dma_channels);
if (rc) {
dev_err(&pdev->dev, "number of channels missing\n");
goto out;
}
rc = device_property_read_u32(&pdev->dev,
"channel-reset-timeout-cycles",
&mgmtdev->chreset_timeout_cycles);
if (rc) {
dev_err(&pdev->dev, "channel reset timeout missing\n");
goto out;
}
rc = device_property_read_u32(&pdev->dev, "max-write-burst-bytes",
&mgmtdev->max_write_request);
if (rc) {
dev_err(&pdev->dev, "max-write-burst-bytes missing\n");
goto out;
}
if (max_write_request &&
(max_write_request != mgmtdev->max_write_request)) {
dev_info(&pdev->dev, "overriding max-write-burst-bytes: %d\n",
max_write_request);
mgmtdev->max_write_request = max_write_request;
} else
max_write_request = mgmtdev->max_write_request;
rc = device_property_read_u32(&pdev->dev, "max-read-burst-bytes",
&mgmtdev->max_read_request);
if (rc) {
dev_err(&pdev->dev, "max-read-burst-bytes missing\n");
goto out;
}
if (max_read_request &&
(max_read_request != mgmtdev->max_read_request)) {
dev_info(&pdev->dev, "overriding max-read-burst-bytes: %d\n",
max_read_request);
mgmtdev->max_read_request = max_read_request;
} else
max_read_request = mgmtdev->max_read_request;
rc = device_property_read_u32(&pdev->dev, "max-write-transactions",
&mgmtdev->max_wr_xactions);
if (rc) {
dev_err(&pdev->dev, "max-write-transactions missing\n");
goto out;
}
if (max_wr_xactions &&
(max_wr_xactions != mgmtdev->max_wr_xactions)) {
dev_info(&pdev->dev, "overriding max-write-transactions: %d\n",
max_wr_xactions);
mgmtdev->max_wr_xactions = max_wr_xactions;
} else
max_wr_xactions = mgmtdev->max_wr_xactions;
rc = device_property_read_u32(&pdev->dev, "max-read-transactions",
&mgmtdev->max_rd_xactions);
if (rc) {
dev_err(&pdev->dev, "max-read-transactions missing\n");
goto out;
}
if (max_rd_xactions &&
(max_rd_xactions != mgmtdev->max_rd_xactions)) {
dev_info(&pdev->dev, "overriding max-read-transactions: %d\n",
max_rd_xactions);
mgmtdev->max_rd_xactions = max_rd_xactions;
} else
max_rd_xactions = mgmtdev->max_rd_xactions;
mgmtdev->priority = devm_kcalloc(&pdev->dev,
mgmtdev->dma_channels,
sizeof(*mgmtdev->priority),
GFP_KERNEL);
if (!mgmtdev->priority) {
rc = -ENOMEM;
goto out;
}
mgmtdev->weight = devm_kcalloc(&pdev->dev,
mgmtdev->dma_channels,
sizeof(*mgmtdev->weight), GFP_KERNEL);
if (!mgmtdev->weight) {
rc = -ENOMEM;
goto out;
}
rc = hidma_mgmt_setup(mgmtdev);
if (rc) {
dev_err(&pdev->dev, "setup failed\n");
goto out;
}
/* start the HW */
val = readl(mgmtdev->virtaddr + HIDMA_CFG_OFFSET);
val |= 1;
writel(val, mgmtdev->virtaddr + HIDMA_CFG_OFFSET);
rc = hidma_mgmt_init_sys(mgmtdev);
if (rc) {
dev_err(&pdev->dev, "sysfs setup failed\n");
goto out;
}
dev_info(&pdev->dev,
"HW rev: %d.%d @ %pa with %d physical channels\n",
mgmtdev->hw_version_major, mgmtdev->hw_version_minor,
&res->start, mgmtdev->dma_channels);
platform_set_drvdata(pdev, mgmtdev);
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(&pdev->dev);
return 0;
out:
pm_runtime_put_sync_suspend(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return rc;
}
#if IS_ENABLED(CONFIG_ACPI)
static const struct acpi_device_id hidma_mgmt_acpi_ids[] = {
{"QCOM8060"},
{},
};
MODULE_DEVICE_TABLE(acpi, hidma_mgmt_acpi_ids);
#endif
static struct platform_driver hidma_mgmt_driver = {
.probe = hidma_mgmt_probe,
.driver = {
.name = "hidma-mgmt",
.acpi_match_table = ACPI_PTR(hidma_mgmt_acpi_ids),
},
};
module_platform_driver(hidma_mgmt_driver);
MODULE_DESCRIPTION("Qualcomm Technologies HIDMA DMA engine interface");
MODULE_LICENSE("GPL v2");
+31
View File
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Qualcomm Technologies HIDMA Management common header
*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*/
struct hidma_mgmt_dev {
u8 hw_version_major;
u8 hw_version_minor;
u32 max_wr_xactions;
u32 max_rd_xactions;
u32 max_write_request;
u32 max_read_request;
u32 dma_channels;
u32 chreset_timeout_cycles;
u32 hw_version;
u32 *priority;
u32 *weight;
/* Hardware device constants */
void __iomem *virtaddr;
resource_size_t addrsize;
struct kobject **chroots;
struct platform_device *pdev;
};
int hidma_mgmt_init_sys(struct hidma_mgmt_dev *dev);
int hidma_mgmt_setup(struct hidma_mgmt_dev *mgmtdev);
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm Technologies HIDMA Management SYS interface
*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*/
#include <linux/sysfs.h>
#include <linux/platform_device.h>
#include "hidma_mgmt.h"
struct hidma_chan_attr {
struct hidma_mgmt_dev *mdev;
int index;
struct kobj_attribute attr;
};
struct hidma_mgmt_fileinfo {
char *name;
int mode;
int (*get)(struct hidma_mgmt_dev *mdev);
int (*set)(struct hidma_mgmt_dev *mdev, u64 val);
};
#define IMPLEMENT_GETSET(name) \
static int get_##name(struct hidma_mgmt_dev *mdev) \
{ \
return mdev->name; \
} \
static int set_##name(struct hidma_mgmt_dev *mdev, u64 val) \
{ \
u64 tmp; \
int rc; \
\
tmp = mdev->name; \
mdev->name = val; \
rc = hidma_mgmt_setup(mdev); \
if (rc) \
mdev->name = tmp; \
return rc; \
}
#define DECLARE_ATTRIBUTE(name, mode) \
{#name, mode, get_##name, set_##name}
IMPLEMENT_GETSET(hw_version_major)
IMPLEMENT_GETSET(hw_version_minor)
IMPLEMENT_GETSET(max_wr_xactions)
IMPLEMENT_GETSET(max_rd_xactions)
IMPLEMENT_GETSET(max_write_request)
IMPLEMENT_GETSET(max_read_request)
IMPLEMENT_GETSET(dma_channels)
IMPLEMENT_GETSET(chreset_timeout_cycles)
static int set_priority(struct hidma_mgmt_dev *mdev, unsigned int i, u64 val)
{
u64 tmp;
int rc;
if (i >= mdev->dma_channels)
return -EINVAL;
tmp = mdev->priority[i];
mdev->priority[i] = val;
rc = hidma_mgmt_setup(mdev);
if (rc)
mdev->priority[i] = tmp;
return rc;
}
static int set_weight(struct hidma_mgmt_dev *mdev, unsigned int i, u64 val)
{
u64 tmp;
int rc;
if (i >= mdev->dma_channels)
return -EINVAL;
tmp = mdev->weight[i];
mdev->weight[i] = val;
rc = hidma_mgmt_setup(mdev);
if (rc)
mdev->weight[i] = tmp;
return rc;
}
static struct hidma_mgmt_fileinfo hidma_mgmt_files[] = {
DECLARE_ATTRIBUTE(hw_version_major, S_IRUGO),
DECLARE_ATTRIBUTE(hw_version_minor, S_IRUGO),
DECLARE_ATTRIBUTE(dma_channels, S_IRUGO),
DECLARE_ATTRIBUTE(chreset_timeout_cycles, S_IRUGO),
DECLARE_ATTRIBUTE(max_wr_xactions, S_IRUGO),
DECLARE_ATTRIBUTE(max_rd_xactions, S_IRUGO),
DECLARE_ATTRIBUTE(max_write_request, S_IRUGO),
DECLARE_ATTRIBUTE(max_read_request, S_IRUGO),
};
static ssize_t show_values(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hidma_mgmt_dev *mdev = dev_get_drvdata(dev);
unsigned int i;
for (i = 0; i < ARRAY_SIZE(hidma_mgmt_files); i++) {
if (strcmp(attr->attr.name, hidma_mgmt_files[i].name) == 0)
return sysfs_emit(buf, "%d\n",
hidma_mgmt_files[i].get(mdev));
}
return 0;
}
static ssize_t set_values(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct hidma_mgmt_dev *mdev = dev_get_drvdata(dev);
unsigned long tmp;
unsigned int i;
int rc;
rc = kstrtoul(buf, 0, &tmp);
if (rc)
return rc;
for (i = 0; i < ARRAY_SIZE(hidma_mgmt_files); i++) {
if (strcmp(attr->attr.name, hidma_mgmt_files[i].name) == 0) {
rc = hidma_mgmt_files[i].set(mdev, tmp);
if (rc)
return rc;
break;
}
}
return count;
}
static ssize_t show_values_channel(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct hidma_chan_attr *chattr;
struct hidma_mgmt_dev *mdev;
chattr = container_of(attr, struct hidma_chan_attr, attr);
mdev = chattr->mdev;
if (strcmp(attr->attr.name, "priority") == 0)
return sysfs_emit(buf, "%d\n", mdev->priority[chattr->index]);
else if (strcmp(attr->attr.name, "weight") == 0)
return sysfs_emit(buf, "%d\n", mdev->weight[chattr->index]);
return 0;
}
static ssize_t set_values_channel(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf,
size_t count)
{
struct hidma_chan_attr *chattr;
struct hidma_mgmt_dev *mdev;
unsigned long tmp;
int rc;
chattr = container_of(attr, struct hidma_chan_attr, attr);
mdev = chattr->mdev;
rc = kstrtoul(buf, 0, &tmp);
if (rc)
return rc;
if (strcmp(attr->attr.name, "priority") == 0) {
rc = set_priority(mdev, chattr->index, tmp);
if (rc)
return rc;
} else if (strcmp(attr->attr.name, "weight") == 0) {
rc = set_weight(mdev, chattr->index, tmp);
if (rc)
return rc;
}
return count;
}
static int create_sysfs_entry(struct hidma_mgmt_dev *dev, char *name, int mode)
{
struct device_attribute *attrs;
char *name_copy;
attrs = devm_kmalloc(&dev->pdev->dev,
sizeof(struct device_attribute), GFP_KERNEL);
if (!attrs)
return -ENOMEM;
name_copy = devm_kstrdup(&dev->pdev->dev, name, GFP_KERNEL);
if (!name_copy)
return -ENOMEM;
attrs->attr.name = name_copy;
attrs->attr.mode = mode;
attrs->show = show_values;
attrs->store = set_values;
sysfs_attr_init(&attrs->attr);
return device_create_file(&dev->pdev->dev, attrs);
}
static int create_sysfs_entry_channel(struct hidma_mgmt_dev *mdev, char *name,
int mode, int index,
struct kobject *parent)
{
struct hidma_chan_attr *chattr;
char *name_copy;
chattr = devm_kmalloc(&mdev->pdev->dev, sizeof(*chattr), GFP_KERNEL);
if (!chattr)
return -ENOMEM;
name_copy = devm_kstrdup(&mdev->pdev->dev, name, GFP_KERNEL);
if (!name_copy)
return -ENOMEM;
chattr->mdev = mdev;
chattr->index = index;
chattr->attr.attr.name = name_copy;
chattr->attr.attr.mode = mode;
chattr->attr.show = show_values_channel;
chattr->attr.store = set_values_channel;
sysfs_attr_init(&chattr->attr.attr);
return sysfs_create_file(parent, &chattr->attr.attr);
}
int hidma_mgmt_init_sys(struct hidma_mgmt_dev *mdev)
{
unsigned int i;
int rc;
int required;
struct kobject *chanops;
required = sizeof(*mdev->chroots) * mdev->dma_channels;
mdev->chroots = devm_kmalloc(&mdev->pdev->dev, required, GFP_KERNEL);
if (!mdev->chroots)
return -ENOMEM;
chanops = kobject_create_and_add("chanops", &mdev->pdev->dev.kobj);
if (!chanops)
return -ENOMEM;
/* create each channel directory here */
for (i = 0; i < mdev->dma_channels; i++) {
char name[20];
snprintf(name, sizeof(name), "chan%d", i);
mdev->chroots[i] = kobject_create_and_add(name, chanops);
if (!mdev->chroots[i])
return -ENOMEM;
}
/* populate common parameters */
for (i = 0; i < ARRAY_SIZE(hidma_mgmt_files); i++) {
rc = create_sysfs_entry(mdev, hidma_mgmt_files[i].name,
hidma_mgmt_files[i].mode);
if (rc)
return rc;
}
/* populate parameters that are per channel */
for (i = 0; i < mdev->dma_channels; i++) {
rc = create_sysfs_entry_channel(mdev, "priority",
(S_IRUGO | S_IWUGO), i,
mdev->chroots[i]);
if (rc)
return rc;
rc = create_sysfs_entry_channel(mdev, "weight",
(S_IRUGO | S_IWUGO), i,
mdev->chroots[i]);
if (rc)
return rc;
}
return 0;
}
EXPORT_SYMBOL_GPL(hidma_mgmt_init_sys);
+950
View File
@@ -0,0 +1,950 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/dma/qcom_adm.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_dma.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include "../dmaengine.h"
#include "../virt-dma.h"
/* ADM registers - calculated from channel number and security domain */
#define ADM_CHAN_MULTI 0x4
#define ADM_CI_MULTI 0x4
#define ADM_CRCI_MULTI 0x4
#define ADM_EE_MULTI 0x800
#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * (chan))
#define ADM_EE_OFFS(ee) (ADM_EE_MULTI * (ee))
#define ADM_CHAN_EE_OFFS(chan, ee) (ADM_CHAN_OFFS(chan) + ADM_EE_OFFS(ee))
#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * (chan))
#define ADM_CI_OFFS(ci) (ADM_CHAN_OFF(ci))
#define ADM_CH_CMD_PTR(chan, ee) (ADM_CHAN_EE_OFFS(chan, ee))
#define ADM_CH_RSLT(chan, ee) (0x40 + ADM_CHAN_EE_OFFS(chan, ee))
#define ADM_CH_FLUSH_STATE0(chan, ee) (0x80 + ADM_CHAN_EE_OFFS(chan, ee))
#define ADM_CH_STATUS_SD(chan, ee) (0x200 + ADM_CHAN_EE_OFFS(chan, ee))
#define ADM_CH_CONF(chan) (0x240 + ADM_CHAN_OFFS(chan))
#define ADM_CH_RSLT_CONF(chan, ee) (0x300 + ADM_CHAN_EE_OFFS(chan, ee))
#define ADM_SEC_DOMAIN_IRQ_STATUS(ee) (0x380 + ADM_EE_OFFS(ee))
#define ADM_CI_CONF(ci) (0x390 + (ci) * ADM_CI_MULTI)
#define ADM_GP_CTL 0x3d8
#define ADM_CRCI_CTL(crci, ee) (0x400 + (crci) * ADM_CRCI_MULTI + \
ADM_EE_OFFS(ee))
/* channel status */
#define ADM_CH_STATUS_VALID BIT(1)
/* channel result */
#define ADM_CH_RSLT_VALID BIT(31)
#define ADM_CH_RSLT_ERR BIT(3)
#define ADM_CH_RSLT_FLUSH BIT(2)
#define ADM_CH_RSLT_TPD BIT(1)
/* channel conf */
#define ADM_CH_CONF_SHADOW_EN BIT(12)
#define ADM_CH_CONF_MPU_DISABLE BIT(11)
#define ADM_CH_CONF_PERM_MPU_CONF BIT(9)
#define ADM_CH_CONF_FORCE_RSLT_EN BIT(7)
#define ADM_CH_CONF_SEC_DOMAIN(ee) ((((ee) & 0x3) << 4) | (((ee) & 0x4) << 11))
/* channel result conf */
#define ADM_CH_RSLT_CONF_FLUSH_EN BIT(1)
#define ADM_CH_RSLT_CONF_IRQ_EN BIT(0)
/* CRCI CTL */
#define ADM_CRCI_CTL_MUX_SEL BIT(18)
#define ADM_CRCI_CTL_RST BIT(17)
/* CI configuration */
#define ADM_CI_RANGE_END(x) ((x) << 24)
#define ADM_CI_RANGE_START(x) ((x) << 16)
#define ADM_CI_BURST_4_WORDS BIT(2)
#define ADM_CI_BURST_8_WORDS BIT(3)
/* GP CTL */
#define ADM_GP_CTL_LP_EN BIT(12)
#define ADM_GP_CTL_LP_CNT(x) ((x) << 8)
/* Command pointer list entry */
#define ADM_CPLE_LP BIT(31)
#define ADM_CPLE_CMD_PTR_LIST BIT(29)
/* Command list entry */
#define ADM_CMD_LC BIT(31)
#define ADM_CMD_DST_CRCI(n) (((n) & 0xf) << 7)
#define ADM_CMD_SRC_CRCI(n) (((n) & 0xf) << 3)
#define ADM_CMD_TYPE_SINGLE 0x0
#define ADM_CMD_TYPE_BOX 0x3
#define ADM_CRCI_MUX_SEL BIT(4)
#define ADM_DESC_ALIGN 8
#define ADM_MAX_XFER (SZ_64K - 1)
#define ADM_MAX_ROWS (SZ_64K - 1)
#define ADM_MAX_CHANNELS 16
struct adm_desc_hw_box {
u32 cmd;
u32 src_addr;
u32 dst_addr;
u32 row_len;
u32 num_rows;
u32 row_offset;
};
struct adm_desc_hw_single {
u32 cmd;
u32 src_addr;
u32 dst_addr;
u32 len;
};
struct adm_async_desc {
struct virt_dma_desc vd;
struct adm_device *adev;
size_t length;
enum dma_transfer_direction dir;
dma_addr_t dma_addr;
size_t dma_len;
void *cpl;
dma_addr_t cp_addr;
u32 crci;
u32 mux;
u32 blk_size;
};
struct adm_chan {
struct virt_dma_chan vc;
struct adm_device *adev;
/* parsed from DT */
u32 id; /* channel id */
struct adm_async_desc *curr_txd;
struct dma_slave_config slave;
u32 crci;
u32 mux;
struct list_head node;
int error;
int initialized;
};
static inline struct adm_chan *to_adm_chan(struct dma_chan *common)
{
return container_of(common, struct adm_chan, vc.chan);
}
struct adm_device {
void __iomem *regs;
struct device *dev;
struct dma_device common;
struct device_dma_parameters dma_parms;
struct adm_chan *channels;
u32 ee;
struct clk *core_clk;
struct clk *iface_clk;
struct reset_control *clk_reset;
struct reset_control *c0_reset;
struct reset_control *c1_reset;
struct reset_control *c2_reset;
int irq;
};
/**
* adm_free_chan - Frees dma resources associated with the specific channel
*
* @chan: dma channel
*
* Free all allocated descriptors associated with this channel
*/
static void adm_free_chan(struct dma_chan *chan)
{
/* free all queued descriptors */
vchan_free_chan_resources(to_virt_chan(chan));
}
/**
* adm_get_blksize - Get block size from burst value
*
* @burst: Burst size of transaction
*/
static int adm_get_blksize(unsigned int burst)
{
int ret;
switch (burst) {
case 16:
case 32:
case 64:
case 128:
ret = ffs(burst >> 4) - 1;
break;
case 192:
ret = 4;
break;
case 256:
ret = 5;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
/**
* adm_process_fc_descriptors - Process descriptors for flow controlled xfers
*
* @achan: ADM channel
* @desc: Descriptor memory pointer
* @sg: Scatterlist entry
* @crci: CRCI value
* @burst: Burst size of transaction
* @direction: DMA transfer direction
*/
static void *adm_process_fc_descriptors(struct adm_chan *achan, void *desc,
struct scatterlist *sg, u32 crci,
u32 burst,
enum dma_transfer_direction direction)
{
struct adm_desc_hw_box *box_desc = NULL;
struct adm_desc_hw_single *single_desc;
u32 remainder = sg_dma_len(sg);
u32 rows, row_offset, crci_cmd;
u32 mem_addr = sg_dma_address(sg);
u32 *incr_addr = &mem_addr;
u32 *src, *dst;
if (direction == DMA_DEV_TO_MEM) {
crci_cmd = ADM_CMD_SRC_CRCI(crci);
row_offset = burst;
src = &achan->slave.src_addr;
dst = &mem_addr;
} else {
crci_cmd = ADM_CMD_DST_CRCI(crci);
row_offset = burst << 16;
src = &mem_addr;
dst = &achan->slave.dst_addr;
}
while (remainder >= burst) {
box_desc = desc;
box_desc->cmd = ADM_CMD_TYPE_BOX | crci_cmd;
box_desc->row_offset = row_offset;
box_desc->src_addr = *src;
box_desc->dst_addr = *dst;
rows = remainder / burst;
rows = min_t(u32, rows, ADM_MAX_ROWS);
box_desc->num_rows = rows << 16 | rows;
box_desc->row_len = burst << 16 | burst;
*incr_addr += burst * rows;
remainder -= burst * rows;
desc += sizeof(*box_desc);
}
/* if leftover bytes, do one single descriptor */
if (remainder) {
single_desc = desc;
single_desc->cmd = ADM_CMD_TYPE_SINGLE | crci_cmd;
single_desc->len = remainder;
single_desc->src_addr = *src;
single_desc->dst_addr = *dst;
desc += sizeof(*single_desc);
if (sg_is_last(sg))
single_desc->cmd |= ADM_CMD_LC;
} else {
if (box_desc && sg_is_last(sg))
box_desc->cmd |= ADM_CMD_LC;
}
return desc;
}
/**
* adm_process_non_fc_descriptors - Process descriptors for non-fc xfers
*
* @achan: ADM channel
* @desc: Descriptor memory pointer
* @sg: Scatterlist entry
* @direction: DMA transfer direction
*/
static void *adm_process_non_fc_descriptors(struct adm_chan *achan, void *desc,
struct scatterlist *sg,
enum dma_transfer_direction direction)
{
struct adm_desc_hw_single *single_desc;
u32 remainder = sg_dma_len(sg);
u32 mem_addr = sg_dma_address(sg);
u32 *incr_addr = &mem_addr;
u32 *src, *dst;
if (direction == DMA_DEV_TO_MEM) {
src = &achan->slave.src_addr;
dst = &mem_addr;
} else {
src = &mem_addr;
dst = &achan->slave.dst_addr;
}
do {
single_desc = desc;
single_desc->cmd = ADM_CMD_TYPE_SINGLE;
single_desc->src_addr = *src;
single_desc->dst_addr = *dst;
single_desc->len = (remainder > ADM_MAX_XFER) ?
ADM_MAX_XFER : remainder;
remainder -= single_desc->len;
*incr_addr += single_desc->len;
desc += sizeof(*single_desc);
} while (remainder);
/* set last command if this is the end of the whole transaction */
if (sg_is_last(sg))
single_desc->cmd |= ADM_CMD_LC;
return desc;
}
/**
* adm_prep_slave_sg - Prep slave sg transaction
*
* @chan: dma channel
* @sgl: scatter gather list
* @sg_len: length of sg
* @direction: DMA transfer direction
* @flags: DMA flags
* @context: transfer context (unused)
*/
static struct dma_async_tx_descriptor *adm_prep_slave_sg(struct dma_chan *chan,
struct scatterlist *sgl,
unsigned int sg_len,
enum dma_transfer_direction direction,
unsigned long flags,
void *context)
{
struct adm_chan *achan = to_adm_chan(chan);
struct adm_device *adev = achan->adev;
struct adm_async_desc *async_desc;
struct scatterlist *sg;
dma_addr_t cple_addr;
u32 i, burst;
u32 single_count = 0, box_count = 0, crci = 0;
void *desc;
u32 *cple;
int blk_size = 0;
if (!is_slave_direction(direction)) {
dev_err(adev->dev, "invalid dma direction\n");
return NULL;
}
/*
* get burst value from slave configuration
*/
burst = (direction == DMA_MEM_TO_DEV) ?
achan->slave.dst_maxburst :
achan->slave.src_maxburst;
/* if using flow control, validate burst and crci values */
if (achan->slave.device_fc) {
blk_size = adm_get_blksize(burst);
if (blk_size < 0) {
dev_err(adev->dev, "invalid burst value: %d\n",
burst);
return NULL;
}
crci = achan->crci & 0xf;
if (!crci || achan->crci > 0x1f) {
dev_err(adev->dev, "invalid crci value\n");
return NULL;
}
}
/* iterate through sgs and compute allocation size of structures */
if (achan->slave.device_fc) {
for_each_sg(sgl, sg, sg_len, i) {
box_count += DIV_ROUND_UP(sg_dma_len(sg) / burst,
ADM_MAX_ROWS);
if (sg_dma_len(sg) % burst)
single_count++;
}
} else {
single_count = sg_nents_for_dma(sgl, sg_len, ADM_MAX_XFER);
}
async_desc = kzalloc_obj(*async_desc, GFP_NOWAIT);
if (!async_desc) {
dev_err(adev->dev, "not enough memory for async_desc struct\n");
return NULL;
}
async_desc->mux = achan->mux ? ADM_CRCI_CTL_MUX_SEL : 0;
async_desc->crci = crci;
async_desc->blk_size = blk_size;
async_desc->dma_len = single_count * sizeof(struct adm_desc_hw_single) +
box_count * sizeof(struct adm_desc_hw_box) +
sizeof(*cple) + 2 * ADM_DESC_ALIGN;
async_desc->cpl = kzalloc(async_desc->dma_len, GFP_NOWAIT);
if (!async_desc->cpl) {
dev_err(adev->dev, "not enough memory for cpl struct\n");
goto free;
}
async_desc->adev = adev;
/* both command list entry and descriptors must be 8 byte aligned */
cple = PTR_ALIGN(async_desc->cpl, ADM_DESC_ALIGN);
desc = PTR_ALIGN(cple + 1, ADM_DESC_ALIGN);
for_each_sg(sgl, sg, sg_len, i) {
async_desc->length += sg_dma_len(sg);
if (achan->slave.device_fc)
desc = adm_process_fc_descriptors(achan, desc, sg, crci,
burst, direction);
else
desc = adm_process_non_fc_descriptors(achan, desc, sg,
direction);
}
async_desc->dma_addr = dma_map_single(adev->dev, async_desc->cpl,
async_desc->dma_len,
DMA_TO_DEVICE);
if (dma_mapping_error(adev->dev, async_desc->dma_addr)) {
dev_err(adev->dev, "dma mapping error for cpl\n");
goto free;
}
cple_addr = async_desc->dma_addr + ((void *)cple - async_desc->cpl);
/* init cmd list */
dma_sync_single_for_cpu(adev->dev, cple_addr, sizeof(*cple),
DMA_TO_DEVICE);
*cple = ADM_CPLE_LP;
*cple |= (async_desc->dma_addr + ADM_DESC_ALIGN) >> 3;
dma_sync_single_for_device(adev->dev, cple_addr, sizeof(*cple),
DMA_TO_DEVICE);
return vchan_tx_prep(&achan->vc, &async_desc->vd, flags);
free:
kfree(async_desc);
return NULL;
}
/**
* adm_terminate_all - terminate all transactions on a channel
* @chan: dma channel
*
* Dequeues and frees all transactions, aborts current transaction
* No callbacks are done
*
*/
static int adm_terminate_all(struct dma_chan *chan)
{
struct adm_chan *achan = to_adm_chan(chan);
struct adm_device *adev = achan->adev;
unsigned long flags;
LIST_HEAD(head);
spin_lock_irqsave(&achan->vc.lock, flags);
vchan_get_all_descriptors(&achan->vc, &head);
/* send flush command to terminate current transaction */
writel_relaxed(0x0,
adev->regs + ADM_CH_FLUSH_STATE0(achan->id, adev->ee));
spin_unlock_irqrestore(&achan->vc.lock, flags);
vchan_dma_desc_free_list(&achan->vc, &head);
return 0;
}
static int adm_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg)
{
struct adm_chan *achan = to_adm_chan(chan);
struct qcom_adm_peripheral_config *config = cfg->peripheral_config;
unsigned long flag;
spin_lock_irqsave(&achan->vc.lock, flag);
memcpy(&achan->slave, cfg, sizeof(struct dma_slave_config));
if (cfg->peripheral_size == sizeof(*config))
achan->crci = config->crci;
spin_unlock_irqrestore(&achan->vc.lock, flag);
return 0;
}
/**
* adm_start_dma - start next transaction
* @achan: ADM dma channel
*/
static void adm_start_dma(struct adm_chan *achan)
{
struct virt_dma_desc *vd = vchan_next_desc(&achan->vc);
struct adm_device *adev = achan->adev;
struct adm_async_desc *async_desc;
lockdep_assert_held(&achan->vc.lock);
if (!vd)
return;
list_del(&vd->node);
/* write next command list out to the CMD FIFO */
async_desc = container_of(vd, struct adm_async_desc, vd);
achan->curr_txd = async_desc;
/* reset channel error */
achan->error = 0;
if (!achan->initialized) {
/* enable interrupts */
writel(ADM_CH_CONF_SHADOW_EN |
ADM_CH_CONF_PERM_MPU_CONF |
ADM_CH_CONF_MPU_DISABLE |
ADM_CH_CONF_SEC_DOMAIN(adev->ee),
adev->regs + ADM_CH_CONF(achan->id));
writel(ADM_CH_RSLT_CONF_IRQ_EN | ADM_CH_RSLT_CONF_FLUSH_EN,
adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee));
achan->initialized = 1;
}
/* set the crci block size if this transaction requires CRCI */
if (async_desc->crci) {
writel(async_desc->mux | async_desc->blk_size,
adev->regs + ADM_CRCI_CTL(async_desc->crci, adev->ee));
}
/* make sure IRQ enable doesn't get reordered */
wmb();
/* write next command list out to the CMD FIFO */
writel(ALIGN(async_desc->dma_addr, ADM_DESC_ALIGN) >> 3,
adev->regs + ADM_CH_CMD_PTR(achan->id, adev->ee));
}
/**
* adm_dma_irq - irq handler for ADM controller
* @irq: IRQ of interrupt
* @data: callback data
*
* IRQ handler for the bam controller
*/
static irqreturn_t adm_dma_irq(int irq, void *data)
{
struct adm_device *adev = data;
u32 srcs, i;
struct adm_async_desc *async_desc;
unsigned long flags;
srcs = readl_relaxed(adev->regs +
ADM_SEC_DOMAIN_IRQ_STATUS(adev->ee));
for (i = 0; i < ADM_MAX_CHANNELS; i++) {
struct adm_chan *achan = &adev->channels[i];
u32 status, result;
if (srcs & BIT(i)) {
status = readl_relaxed(adev->regs +
ADM_CH_STATUS_SD(i, adev->ee));
/* if no result present, skip */
if (!(status & ADM_CH_STATUS_VALID))
continue;
result = readl_relaxed(adev->regs +
ADM_CH_RSLT(i, adev->ee));
/* no valid results, skip */
if (!(result & ADM_CH_RSLT_VALID))
continue;
/* flag error if transaction was flushed or failed */
if (result & (ADM_CH_RSLT_ERR | ADM_CH_RSLT_FLUSH))
achan->error = 1;
spin_lock_irqsave(&achan->vc.lock, flags);
async_desc = achan->curr_txd;
achan->curr_txd = NULL;
if (async_desc) {
vchan_cookie_complete(&async_desc->vd);
/* kick off next DMA */
adm_start_dma(achan);
}
spin_unlock_irqrestore(&achan->vc.lock, flags);
}
}
return IRQ_HANDLED;
}
/**
* adm_tx_status - returns status of transaction
* @chan: dma channel
* @cookie: transaction cookie
* @txstate: DMA transaction state
*
* Return status of dma transaction
*/
static enum dma_status adm_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
struct dma_tx_state *txstate)
{
struct adm_chan *achan = to_adm_chan(chan);
struct virt_dma_desc *vd;
enum dma_status ret;
unsigned long flags;
size_t residue = 0;
ret = dma_cookie_status(chan, cookie, txstate);
if (ret == DMA_COMPLETE || !txstate)
return ret;
spin_lock_irqsave(&achan->vc.lock, flags);
vd = vchan_find_desc(&achan->vc, cookie);
if (vd)
residue = container_of(vd, struct adm_async_desc, vd)->length;
spin_unlock_irqrestore(&achan->vc.lock, flags);
/*
* residue is either the full length if it is in the issued list, or 0
* if it is in progress. We have no reliable way of determining
* anything in between
*/
dma_set_residue(txstate, residue);
if (achan->error)
return DMA_ERROR;
return ret;
}
/**
* adm_issue_pending - starts pending transactions
* @chan: dma channel
*
* Issues all pending transactions and starts DMA
*/
static void adm_issue_pending(struct dma_chan *chan)
{
struct adm_chan *achan = to_adm_chan(chan);
unsigned long flags;
spin_lock_irqsave(&achan->vc.lock, flags);
if (vchan_issue_pending(&achan->vc) && !achan->curr_txd)
adm_start_dma(achan);
spin_unlock_irqrestore(&achan->vc.lock, flags);
}
/**
* adm_dma_free_desc - free descriptor memory
* @vd: virtual descriptor
*
*/
static void adm_dma_free_desc(struct virt_dma_desc *vd)
{
struct adm_async_desc *async_desc = container_of(vd,
struct adm_async_desc, vd);
dma_unmap_single(async_desc->adev->dev, async_desc->dma_addr,
async_desc->dma_len, DMA_TO_DEVICE);
kfree(async_desc->cpl);
kfree(async_desc);
}
static void adm_channel_init(struct adm_device *adev, struct adm_chan *achan,
u32 index)
{
achan->id = index;
achan->adev = adev;
vchan_init(&achan->vc, &adev->common);
achan->vc.desc_free = adm_dma_free_desc;
}
/**
* adm_dma_xlate
* @dma_spec: pointer to DMA specifier as found in the device tree
* @ofdma: pointer to DMA controller data
*
* This can use either 1-cell or 2-cell formats, the first cell
* identifies the slave device, while the optional second cell
* contains the crci value.
*
* Returns pointer to appropriate dma channel on success or NULL on error.
*/
static struct dma_chan *adm_dma_xlate(struct of_phandle_args *dma_spec,
struct of_dma *ofdma)
{
struct dma_device *dev = ofdma->of_dma_data;
struct dma_chan *chan, *candidate = NULL;
struct adm_chan *achan;
if (!dev || dma_spec->args_count > 2)
return NULL;
list_for_each_entry(chan, &dev->channels, device_node)
if (chan->chan_id == dma_spec->args[0]) {
candidate = chan;
break;
}
if (!candidate)
return NULL;
achan = to_adm_chan(candidate);
if (dma_spec->args_count == 2)
achan->crci = dma_spec->args[1];
else
achan->crci = 0;
return dma_get_slave_channel(candidate);
}
static int adm_dma_probe(struct platform_device *pdev)
{
struct adm_device *adev;
int ret;
u32 i;
adev = devm_kzalloc(&pdev->dev, sizeof(*adev), GFP_KERNEL);
if (!adev)
return -ENOMEM;
adev->dev = &pdev->dev;
adev->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(adev->regs))
return PTR_ERR(adev->regs);
adev->irq = platform_get_irq(pdev, 0);
if (adev->irq < 0)
return adev->irq;
ret = of_property_read_u32(pdev->dev.of_node, "qcom,ee", &adev->ee);
if (ret) {
dev_err(adev->dev, "Execution environment unspecified\n");
return ret;
}
adev->core_clk = devm_clk_get(adev->dev, "core");
if (IS_ERR(adev->core_clk))
return PTR_ERR(adev->core_clk);
adev->iface_clk = devm_clk_get(adev->dev, "iface");
if (IS_ERR(adev->iface_clk))
return PTR_ERR(adev->iface_clk);
adev->clk_reset = devm_reset_control_get_exclusive(&pdev->dev, "clk");
if (IS_ERR(adev->clk_reset)) {
dev_err(adev->dev, "failed to get ADM0 reset\n");
return PTR_ERR(adev->clk_reset);
}
adev->c0_reset = devm_reset_control_get_exclusive(&pdev->dev, "c0");
if (IS_ERR(adev->c0_reset)) {
dev_err(adev->dev, "failed to get ADM0 C0 reset\n");
return PTR_ERR(adev->c0_reset);
}
adev->c1_reset = devm_reset_control_get_exclusive(&pdev->dev, "c1");
if (IS_ERR(adev->c1_reset)) {
dev_err(adev->dev, "failed to get ADM0 C1 reset\n");
return PTR_ERR(adev->c1_reset);
}
adev->c2_reset = devm_reset_control_get_exclusive(&pdev->dev, "c2");
if (IS_ERR(adev->c2_reset)) {
dev_err(adev->dev, "failed to get ADM0 C2 reset\n");
return PTR_ERR(adev->c2_reset);
}
ret = clk_prepare_enable(adev->core_clk);
if (ret) {
dev_err(adev->dev, "failed to prepare/enable core clock\n");
return ret;
}
ret = clk_prepare_enable(adev->iface_clk);
if (ret) {
dev_err(adev->dev, "failed to prepare/enable iface clock\n");
goto err_disable_core_clk;
}
reset_control_assert(adev->clk_reset);
reset_control_assert(adev->c0_reset);
reset_control_assert(adev->c1_reset);
reset_control_assert(adev->c2_reset);
udelay(2);
reset_control_deassert(adev->clk_reset);
reset_control_deassert(adev->c0_reset);
reset_control_deassert(adev->c1_reset);
reset_control_deassert(adev->c2_reset);
adev->channels = devm_kcalloc(adev->dev, ADM_MAX_CHANNELS,
sizeof(*adev->channels), GFP_KERNEL);
if (!adev->channels) {
ret = -ENOMEM;
goto err_disable_clks;
}
/* allocate and initialize channels */
INIT_LIST_HEAD(&adev->common.channels);
for (i = 0; i < ADM_MAX_CHANNELS; i++)
adm_channel_init(adev, &adev->channels[i], i);
/* reset CRCIs */
for (i = 0; i < 16; i++)
writel(ADM_CRCI_CTL_RST, adev->regs +
ADM_CRCI_CTL(i, adev->ee));
/* configure client interfaces */
writel(ADM_CI_RANGE_START(0x40) | ADM_CI_RANGE_END(0xb0) |
ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(0));
writel(ADM_CI_RANGE_START(0x2a) | ADM_CI_RANGE_END(0x2c) |
ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(1));
writel(ADM_CI_RANGE_START(0x12) | ADM_CI_RANGE_END(0x28) |
ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(2));
writel(ADM_GP_CTL_LP_EN | ADM_GP_CTL_LP_CNT(0xf),
adev->regs + ADM_GP_CTL);
ret = devm_request_irq(adev->dev, adev->irq, adm_dma_irq,
0, "adm_dma", adev);
if (ret)
goto err_disable_clks;
platform_set_drvdata(pdev, adev);
adev->common.dev = adev->dev;
adev->common.dev->dma_parms = &adev->dma_parms;
/* set capabilities */
dma_cap_zero(adev->common.cap_mask);
dma_cap_set(DMA_SLAVE, adev->common.cap_mask);
dma_cap_set(DMA_PRIVATE, adev->common.cap_mask);
/* initialize dmaengine apis */
adev->common.directions = BIT(DMA_DEV_TO_MEM | DMA_MEM_TO_DEV);
adev->common.residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
adev->common.src_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES;
adev->common.dst_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES;
adev->common.device_free_chan_resources = adm_free_chan;
adev->common.device_prep_slave_sg = adm_prep_slave_sg;
adev->common.device_issue_pending = adm_issue_pending;
adev->common.device_tx_status = adm_tx_status;
adev->common.device_terminate_all = adm_terminate_all;
adev->common.device_config = adm_slave_config;
ret = dma_async_device_register(&adev->common);
if (ret) {
dev_err(adev->dev, "failed to register dma async device\n");
goto err_disable_clks;
}
ret = of_dma_controller_register(pdev->dev.of_node, adm_dma_xlate,
&adev->common);
if (ret)
goto err_unregister_dma;
return 0;
err_unregister_dma:
dma_async_device_unregister(&adev->common);
err_disable_clks:
clk_disable_unprepare(adev->iface_clk);
err_disable_core_clk:
clk_disable_unprepare(adev->core_clk);
return ret;
}
static void adm_dma_remove(struct platform_device *pdev)
{
struct adm_device *adev = platform_get_drvdata(pdev);
struct adm_chan *achan;
u32 i;
of_dma_controller_free(pdev->dev.of_node);
dma_async_device_unregister(&adev->common);
for (i = 0; i < ADM_MAX_CHANNELS; i++) {
achan = &adev->channels[i];
/* mask IRQs for this channel/EE pair */
writel(0, adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee));
tasklet_kill(&adev->channels[i].vc.task);
adm_terminate_all(&adev->channels[i].vc.chan);
}
devm_free_irq(adev->dev, adev->irq, adev);
clk_disable_unprepare(adev->core_clk);
clk_disable_unprepare(adev->iface_clk);
}
static const struct of_device_id adm_of_match[] = {
{ .compatible = "qcom,adm", },
{}
};
MODULE_DEVICE_TABLE(of, adm_of_match);
static struct platform_driver adm_dma_driver = {
.probe = adm_dma_probe,
.remove = adm_dma_remove,
.driver = {
.name = "adm-dma-engine",
.of_match_table = adm_of_match,
},
};
module_platform_driver(adm_dma_driver);
MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>");
MODULE_DESCRIPTION("QCOM ADM DMA engine driver");
MODULE_LICENSE("GPL v2");