* [PATCH v9 07/15] ASoC: qdsp6: q6asm: Add support to memory map and unmap
From: Srinivas Kandagatla @ 2018-05-18 12:56 UTC (permalink / raw)
To: broonie, linux-arm-msm, alsa-devel, bgoswami
Cc: robh+dt, gregkh, david.brown, mark.rutland, lgirdwood, plai,
tiwai, perex, devicetree, linux-kernel, linux-arm-kernel,
rohkumar, spatakok, Srinivas Kandagatla
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds support to memory map and unmap regions commands in
q6asm module.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: Rohit kumar <rohitkr@codeaurora.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/qdsp6/q6asm.c | 347 +++++++++++++++++++++++++++++++++++++++++++
sound/soc/qcom/qdsp6/q6asm.h | 5 +
2 files changed, 352 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c
index 585fcfbada6a..a20d243ed10a 100644
--- a/sound/soc/qcom/qdsp6/q6asm.c
+++ b/sound/soc/qcom/qdsp6/q6asm.c
@@ -19,10 +19,44 @@
#include "q6dsp-errno.h"
#include "q6dsp-common.h"
+#define ASM_CMD_SHARED_MEM_MAP_REGIONS 0x00010D92
+#define ASM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010D93
+#define ASM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010D94
+
#define ASM_SYNC_IO_MODE 0x0001
#define ASM_ASYNC_IO_MODE 0x0002
#define ASM_TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */
#define ASM_TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */
+#define ASM_SHIFT_GAPLESS_MODE_FLAG 31
+#define ADSP_MEMORY_MAP_SHMEM8_4K_POOL 3
+
+struct avs_cmd_shared_mem_map_regions {
+ u16 mem_pool_id;
+ u16 num_regions;
+ u32 property_flag;
+} __packed;
+
+struct avs_shared_map_region_payload {
+ u32 shm_addr_lsw;
+ u32 shm_addr_msw;
+ u32 mem_size_bytes;
+} __packed;
+
+struct avs_cmd_shared_mem_unmap_regions {
+ u32 mem_map_handle;
+} __packed;
+
+struct audio_buffer {
+ phys_addr_t phys;
+ uint32_t size; /* size of buffer */
+};
+
+struct audio_port_data {
+ struct audio_buffer *buf;
+ uint32_t num_periods;
+ uint32_t dsp_buf;
+ uint32_t mem_map_handle;
+};
struct q6asm {
struct apr_device *adev;
@@ -44,6 +78,8 @@ struct audio_client {
struct mutex cmd_lock;
spinlock_t lock;
struct kref refcount;
+ /* idx:1 out port, 0: in port */
+ struct audio_port_data port[2];
wait_queue_head_t cmd_wait;
struct aprv2_ibasic_rsp_result_t result;
int perf_mode;
@@ -52,6 +88,275 @@ struct audio_client {
struct device *dev;
};
+static inline void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr,
+ uint32_t pkt_size, bool cmd_flg,
+ uint32_t stream_id)
+{
+ hdr->hdr_field = APR_SEQ_CMD_HDR_FIELD;
+ hdr->src_port = ((ac->session << 8) & 0xFF00) | (stream_id);
+ hdr->dest_port = ((ac->session << 8) & 0xFF00) | (stream_id);
+ hdr->pkt_size = pkt_size;
+ if (cmd_flg)
+ hdr->token = ac->session;
+}
+
+static int q6asm_apr_send_session_pkt(struct q6asm *a, struct audio_client *ac,
+ struct apr_pkt *pkt, uint32_t rsp_opcode)
+{
+ struct apr_hdr *hdr = &pkt->hdr;
+ int rc;
+
+ mutex_lock(&ac->cmd_lock);
+ ac->result.opcode = 0;
+ ac->result.status = 0;
+ rc = apr_send_pkt(a->adev, pkt);
+ if (rc < 0)
+ goto err;
+
+ if (rsp_opcode)
+ rc = wait_event_timeout(a->mem_wait,
+ (ac->result.opcode == hdr->opcode) ||
+ (ac->result.opcode == rsp_opcode),
+ 5 * HZ);
+ else
+ rc = wait_event_timeout(a->mem_wait,
+ (ac->result.opcode == hdr->opcode),
+ 5 * HZ);
+
+ if (!rc) {
+ dev_err(a->dev, "CMD timeout\n");
+ rc = -ETIMEDOUT;
+ } else if (ac->result.status > 0) {
+ dev_err(a->dev, "DSP returned error[%x]\n",
+ ac->result.status);
+ rc = -EINVAL;
+ }
+
+err:
+ mutex_unlock(&ac->cmd_lock);
+ return rc;
+}
+
+static int __q6asm_memory_unmap(struct audio_client *ac,
+ phys_addr_t buf_add, int dir)
+{
+ struct avs_cmd_shared_mem_unmap_regions *mem_unmap;
+ struct q6asm *a = dev_get_drvdata(ac->dev->parent);
+ struct apr_pkt *pkt;
+ int rc, pkt_size;
+ void *p;
+
+ if (ac->port[dir].mem_map_handle == 0) {
+ dev_err(ac->dev, "invalid mem handle\n");
+ return -EINVAL;
+ }
+
+ pkt_size = APR_HDR_SIZE + sizeof(*mem_unmap);
+ p = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ mem_unmap = p + APR_HDR_SIZE;
+
+ pkt->hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.token = ((ac->session << 8) | dir);
+
+ pkt->hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS;
+ mem_unmap->mem_map_handle = ac->port[dir].mem_map_handle;
+
+ rc = q6asm_apr_send_session_pkt(a, ac, pkt, 0);
+ if (rc < 0) {
+ kfree(pkt);
+ return rc;
+ }
+
+ ac->port[dir].mem_map_handle = 0;
+
+ kfree(pkt);
+ return 0;
+}
+
+
+static void q6asm_audio_client_free_buf(struct audio_client *ac,
+ struct audio_port_data *port)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ac->lock, flags);
+ port->num_periods = 0;
+ kfree(port->buf);
+ port->buf = NULL;
+ spin_unlock_irqrestore(&ac->lock, flags);
+}
+
+/**
+ * q6asm_unmap_memory_regions() - unmap memory regions in the dsp.
+ *
+ * @dir: direction of audio stream
+ * @ac: audio client instanace
+ *
+ * Return: Will be an negative value on failure or zero on success
+ */
+int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac)
+{
+ struct audio_port_data *port;
+ int cnt = 0;
+ int rc = 0;
+
+ port = &ac->port[dir];
+ if (!port->buf) {
+ rc = -EINVAL;
+ goto err;
+ }
+
+ cnt = port->num_periods - 1;
+ if (cnt >= 0) {
+ rc = __q6asm_memory_unmap(ac, port->buf[dir].phys, dir);
+ if (rc < 0) {
+ dev_err(ac->dev, "%s: Memory_unmap_regions failed %d\n",
+ __func__, rc);
+ goto err;
+ }
+ }
+
+ q6asm_audio_client_free_buf(ac, port);
+
+err:
+ return rc;
+}
+EXPORT_SYMBOL_GPL(q6asm_unmap_memory_regions);
+
+static int __q6asm_memory_map_regions(struct audio_client *ac, int dir,
+ size_t period_sz, unsigned int periods,
+ bool is_contiguous)
+{
+ struct avs_cmd_shared_mem_map_regions *cmd = NULL;
+ struct avs_shared_map_region_payload *mregions = NULL;
+ struct q6asm *a = dev_get_drvdata(ac->dev->parent);
+ struct audio_port_data *port = NULL;
+ struct audio_buffer *ab = NULL;
+ struct apr_pkt *pkt;
+ void *p;
+ unsigned long flags;
+ uint32_t num_regions, buf_sz;
+ int rc, i, pkt_size;
+
+ if (is_contiguous) {
+ num_regions = 1;
+ buf_sz = period_sz * periods;
+ } else {
+ buf_sz = period_sz;
+ num_regions = periods;
+ }
+
+ /* DSP expects size should be aligned to 4K */
+ buf_sz = ALIGN(buf_sz, 4096);
+
+ pkt_size = APR_HDR_SIZE + sizeof(*cmd) +
+ (sizeof(*mregions) * num_regions);
+
+ p = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ cmd = p + APR_HDR_SIZE;
+ mregions = p + APR_HDR_SIZE + sizeof(*cmd);
+
+ pkt->hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.token = ((ac->session << 8) | dir);
+ pkt->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS;
+
+ cmd->mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL;
+ cmd->num_regions = num_regions;
+ cmd->property_flag = 0x00;
+
+ spin_lock_irqsave(&ac->lock, flags);
+ port = &ac->port[dir];
+
+ for (i = 0; i < num_regions; i++) {
+ ab = &port->buf[i];
+ mregions->shm_addr_lsw = lower_32_bits(ab->phys);
+ mregions->shm_addr_msw = upper_32_bits(ab->phys);
+ mregions->mem_size_bytes = buf_sz;
+ ++mregions;
+ }
+ spin_unlock_irqrestore(&ac->lock, flags);
+
+ rc = q6asm_apr_send_session_pkt(a, ac, pkt,
+ ASM_CMDRSP_SHARED_MEM_MAP_REGIONS);
+
+ kfree(pkt);
+
+ return rc;
+}
+
+/**
+ * q6asm_map_memory_regions() - map memory regions in the dsp.
+ *
+ * @dir: direction of audio stream
+ * @ac: audio client instanace
+ * @phys: physcial address that needs mapping.
+ * @period_sz: audio period size
+ * @periods: number of periods
+ *
+ * Return: Will be an negative value on failure or zero on success
+ */
+int q6asm_map_memory_regions(unsigned int dir, struct audio_client *ac,
+ phys_addr_t phys,
+ size_t period_sz, unsigned int periods)
+{
+ struct audio_buffer *buf;
+ unsigned long flags;
+ int cnt;
+ int rc;
+
+ spin_lock_irqsave(&ac->lock, flags);
+ if (ac->port[dir].buf) {
+ dev_err(ac->dev, "Buffer already allocated\n");
+ spin_unlock_irqrestore(&ac->lock, flags);
+ return 0;
+ }
+
+ buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_ATOMIC);
+ if (!buf) {
+ spin_unlock_irqrestore(&ac->lock, flags);
+ return -ENOMEM;
+ }
+
+
+ ac->port[dir].buf = buf;
+
+ buf[0].phys = phys;
+ buf[0].size = period_sz;
+
+ for (cnt = 1; cnt < periods; cnt++) {
+ if (period_sz > 0) {
+ buf[cnt].phys = buf[0].phys + (cnt * period_sz);
+ buf[cnt].size = period_sz;
+ }
+ }
+ ac->port[dir].num_periods = periods;
+
+ spin_unlock_irqrestore(&ac->lock, flags);
+
+ rc = __q6asm_memory_map_regions(ac, dir, period_sz, periods, 1);
+ if (rc < 0) {
+ dev_err(ac->dev, "Memory_map_regions failed\n");
+ q6asm_audio_client_free_buf(ac, &ac->port[dir]);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(q6asm_map_memory_regions);
+
static void q6asm_audio_client_release(struct kref *ref)
{
struct audio_client *ac;
@@ -108,9 +413,13 @@ static int q6asm_srvc_callback(struct apr_device *adev,
struct apr_resp_pkt *data)
{
struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
+ struct aprv2_ibasic_rsp_result_t *result;
+ struct audio_port_data *port;
struct audio_client *ac = NULL;
struct apr_hdr *hdr = &data->hdr;
+ struct q6asm *a;
uint32_t sid = 0;
+ uint32_t dir = 0;
sid = (hdr->token >> 8) & 0x0F;
ac = q6asm_get_audio_client(q6asm, sid);
@@ -119,9 +428,47 @@ static int q6asm_srvc_callback(struct apr_device *adev,
return 0;
}
+ a = dev_get_drvdata(ac->dev->parent);
+ dir = (hdr->token & 0x0F);
+ port = &ac->port[dir];
+ result = data->payload;
+
+ switch (hdr->opcode) {
+ case APR_BASIC_RSP_RESULT:
+ switch (result->opcode) {
+ case ASM_CMD_SHARED_MEM_MAP_REGIONS:
+ case ASM_CMD_SHARED_MEM_UNMAP_REGIONS:
+ ac->result = *result;
+ wake_up(&a->mem_wait);
+ break;
+ default:
+ dev_err(&adev->dev, "command[0x%x] not expecting rsp\n",
+ result->opcode);
+ break;
+ }
+ goto done;
+ case ASM_CMDRSP_SHARED_MEM_MAP_REGIONS:
+ ac->result.status = 0;
+ ac->result.opcode = hdr->opcode;
+ port->mem_map_handle = result->opcode;
+ wake_up(&a->mem_wait);
+ break;
+ case ASM_CMD_SHARED_MEM_UNMAP_REGIONS:
+ ac->result.opcode = hdr->opcode;
+ ac->result.status = 0;
+ port->mem_map_handle = 0;
+ wake_up(&a->mem_wait);
+ break;
+ default:
+ dev_dbg(&adev->dev, "command[0x%x]success [0x%x]\n",
+ result->opcode, result->status);
+ break;
+ }
+
if (ac->cb)
ac->cb(hdr->opcode, hdr->token, data->payload, ac->priv);
+done:
kref_put(&ac->refcount, q6asm_audio_client_release);
return 0;
diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h
index b7816e6384e7..8c317b7b63c3 100644
--- a/sound/soc/qcom/qdsp6/q6asm.h
+++ b/sound/soc/qcom/qdsp6/q6asm.h
@@ -12,4 +12,9 @@ struct audio_client *q6asm_audio_client_alloc(struct device *dev,
int session_id, int perf_mode);
void q6asm_audio_client_free(struct audio_client *ac);
int q6asm_get_session_id(struct audio_client *ac);
+int q6asm_map_memory_regions(unsigned int dir,
+ struct audio_client *ac,
+ phys_addr_t phys,
+ size_t bufsz, unsigned int bufcnt);
+int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac);
#endif /* __Q6_ASM_H__ */
--
2.16.2
^ permalink raw reply related
* [PATCH v9 06/15] ASoC: qdsp6: q6asm: Add q6asm driver
From: Srinivas Kandagatla @ 2018-05-18 12:56 UTC (permalink / raw)
To: broonie, linux-arm-msm, alsa-devel, bgoswami
Cc: mark.rutland, devicetree, rohkumar, gregkh, plai, lgirdwood,
tiwai, david.brown, robh+dt, Srinivas Kandagatla,
linux-arm-kernel, spatakok, linux-kernel
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds basic support to Q6 ASM (Audio Stream Manager) module on
Q6DSP. ASM supports up to 8 concurrent streams. each stream can be setup
as playback/capture. ASM provides top control functions like
Pause/flush/resume for playback and record. ASM can Create/destroy encoder,
decoder and also provides POPP dynamic services.
This patch adds support to basic features to allow hdmi playback.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: Rohit kumar <rohitkr@codeaurora.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/Kconfig | 4 +
sound/soc/qcom/qdsp6/Makefile | 1 +
sound/soc/qcom/qdsp6/q6asm.c | 252 ++++++++++++++++++++++++++++++++++++++++++
sound/soc/qcom/qdsp6/q6asm.h | 15 +++
4 files changed, 272 insertions(+)
create mode 100644 sound/soc/qcom/qdsp6/q6asm.c
create mode 100644 sound/soc/qcom/qdsp6/q6asm.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index 971127edbc23..941774abd94f 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -53,6 +53,9 @@ config SND_SOC_QDSP6_AFE
config SND_SOC_QDSP6_ADM
tristate
+config SND_SOC_QDSP6_ASM
+ tristate
+
config SND_SOC_QDSP6
tristate "SoC ALSA audio driver for QDSP6"
depends on QCOM_APR && HAS_DMA
@@ -60,6 +63,7 @@ config SND_SOC_QDSP6
select SND_SOC_QDSP6_CORE
select SND_SOC_QDSP6_AFE
select SND_SOC_QDSP6_ADM
+ select SND_SOC_QDSP6_ASM
help
To add support for MSM QDSP6 Soc Audio.
This will enable sound soc platform specific
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile
index 95cdb3a12694..01d9dcf3375c 100644
--- a/sound/soc/qcom/qdsp6/Makefile
+++ b/sound/soc/qcom/qdsp6/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o
obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o
obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o
obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o
+obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c
new file mode 100644
index 000000000000..585fcfbada6a
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6asm.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <linux/soc/qcom/apr.h>
+#include <linux/device.h>
+#include <linux/of_platform.h>
+#include <linux/spinlock.h>
+#include <linux/kref.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include "q6asm.h"
+#include "q6core.h"
+#include "q6dsp-errno.h"
+#include "q6dsp-common.h"
+
+#define ASM_SYNC_IO_MODE 0x0001
+#define ASM_ASYNC_IO_MODE 0x0002
+#define ASM_TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */
+#define ASM_TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */
+
+struct q6asm {
+ struct apr_device *adev;
+ struct device *dev;
+ struct q6core_svc_api_info ainfo;
+ wait_queue_head_t mem_wait;
+ struct platform_device *pcmdev;
+ spinlock_t slock;
+ struct audio_client *session[MAX_SESSIONS + 1];
+ struct platform_device *pdev_dais;
+};
+
+struct audio_client {
+ int session;
+ q6asm_cb cb;
+ void *priv;
+ uint32_t io_mode;
+ struct apr_device *adev;
+ struct mutex cmd_lock;
+ spinlock_t lock;
+ struct kref refcount;
+ wait_queue_head_t cmd_wait;
+ struct aprv2_ibasic_rsp_result_t result;
+ int perf_mode;
+ int stream_id;
+ struct q6asm *q6asm;
+ struct device *dev;
+};
+
+static void q6asm_audio_client_release(struct kref *ref)
+{
+ struct audio_client *ac;
+ struct q6asm *a;
+ unsigned long flags;
+
+ ac = container_of(ref, struct audio_client, refcount);
+ a = ac->q6asm;
+
+ spin_lock_irqsave(&a->slock, flags);
+ a->session[ac->session] = NULL;
+ spin_unlock_irqrestore(&a->slock, flags);
+
+ kfree(ac);
+}
+
+/**
+ * q6asm_audio_client_free() - Freee allocated audio client
+ *
+ * @ac: audio client to free
+ */
+void q6asm_audio_client_free(struct audio_client *ac)
+{
+ kref_put(&ac->refcount, q6asm_audio_client_release);
+}
+EXPORT_SYMBOL_GPL(q6asm_audio_client_free);
+
+static struct audio_client *q6asm_get_audio_client(struct q6asm *a,
+ int session_id)
+{
+ struct audio_client *ac = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&a->slock, flags);
+ if ((session_id <= 0) || (session_id > MAX_SESSIONS)) {
+ dev_err(a->dev, "invalid session: %d\n", session_id);
+ goto err;
+ }
+
+ /* check for valid session */
+ if (!a->session[session_id])
+ goto err;
+ else if (a->session[session_id]->session != session_id)
+ goto err;
+
+ ac = a->session[session_id];
+ kref_get(&ac->refcount);
+err:
+ spin_unlock_irqrestore(&a->slock, flags);
+ return ac;
+}
+
+static int q6asm_srvc_callback(struct apr_device *adev,
+ struct apr_resp_pkt *data)
+{
+ struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
+ struct audio_client *ac = NULL;
+ struct apr_hdr *hdr = &data->hdr;
+ uint32_t sid = 0;
+
+ sid = (hdr->token >> 8) & 0x0F;
+ ac = q6asm_get_audio_client(q6asm, sid);
+ if (!ac) {
+ dev_err(&adev->dev, "Audio Client not active\n");
+ return 0;
+ }
+
+ if (ac->cb)
+ ac->cb(hdr->opcode, hdr->token, data->payload, ac->priv);
+
+ kref_put(&ac->refcount, q6asm_audio_client_release);
+
+ return 0;
+}
+
+/**
+ * q6asm_get_session_id() - get session id for audio client
+ *
+ * @c: audio client pointer
+ *
+ * Return: Will be an session id of the audio client.
+ */
+int q6asm_get_session_id(struct audio_client *c)
+{
+ return c->session;
+}
+EXPORT_SYMBOL_GPL(q6asm_get_session_id);
+
+/**
+ * q6asm_audio_client_alloc() - Allocate a new audio client
+ *
+ * @dev: Pointer to asm child device.
+ * @cb: event callback.
+ * @priv: private data associated with this client.
+ * @stream_id: stream id
+ * @perf_mode: performace mode for this client
+ *
+ * Return: Will be an error pointer on error or a valid audio client
+ * on success.
+ */
+struct audio_client *q6asm_audio_client_alloc(struct device *dev, q6asm_cb cb,
+ void *priv, int stream_id,
+ int perf_mode)
+{
+ struct q6asm *a = dev_get_drvdata(dev->parent);
+ struct audio_client *ac;
+ unsigned long flags;
+
+ ac = q6asm_get_audio_client(a, stream_id + 1);
+ if (ac) {
+ dev_err(dev, "Audio Client already active\n");
+ return ac;
+ }
+
+ ac = kzalloc(sizeof(*ac), GFP_KERNEL);
+ if (!ac)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_irqsave(&a->slock, flags);
+ a->session[stream_id + 1] = ac;
+ spin_unlock_irqrestore(&a->slock, flags);
+ ac->session = stream_id + 1;
+ ac->cb = cb;
+ ac->dev = dev;
+ ac->q6asm = a;
+ ac->priv = priv;
+ ac->io_mode = ASM_SYNC_IO_MODE;
+ ac->perf_mode = perf_mode;
+ /* DSP expects stream id from 1 */
+ ac->stream_id = 1;
+ ac->adev = a->adev;
+ kref_init(&ac->refcount);
+
+ init_waitqueue_head(&ac->cmd_wait);
+ mutex_init(&ac->cmd_lock);
+ spin_lock_init(&ac->lock);
+
+ return ac;
+}
+EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc);
+
+
+static int q6asm_probe(struct apr_device *adev)
+{
+ struct device *dev = &adev->dev;
+ struct device_node *dais_np;
+ struct q6asm *q6asm;
+
+ q6asm = devm_kzalloc(dev, sizeof(*q6asm), GFP_KERNEL);
+ if (!q6asm)
+ return -ENOMEM;
+
+ q6core_get_svc_api_info(adev->svc_id, &q6asm->ainfo);
+
+ q6asm->dev = dev;
+ q6asm->adev = adev;
+ init_waitqueue_head(&q6asm->mem_wait);
+ spin_lock_init(&q6asm->slock);
+ dev_set_drvdata(dev, q6asm);
+
+ dais_np = of_get_child_by_name(dev->of_node, "dais");
+ if (dais_np) {
+ q6asm->pdev_dais = of_platform_device_create(dais_np,
+ "q6asm-dai", dev);
+ of_node_put(dais_np);
+ }
+
+ return 0;
+}
+
+static int q6asm_remove(struct apr_device *adev)
+{
+ struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
+
+ if (q6asm->pdev_dais)
+ of_platform_device_destroy(&q6asm->pdev_dais->dev, NULL);
+
+ return 0;
+}
+static const struct of_device_id q6asm_device_id[] = {
+ { .compatible = "qcom,q6asm" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6asm_device_id);
+
+static struct apr_driver qcom_q6asm_driver = {
+ .probe = q6asm_probe,
+ .remove = q6asm_remove,
+ .callback = q6asm_srvc_callback,
+ .driver = {
+ .name = "qcom-q6asm",
+ .of_match_table = of_match_ptr(q6asm_device_id),
+ },
+};
+
+module_apr_driver(qcom_q6asm_driver);
+MODULE_DESCRIPTION("Q6 Audio Stream Manager driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h
new file mode 100644
index 000000000000..b7816e6384e7
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6asm.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __Q6_ASM_H__
+#define __Q6_ASM_H__
+
+#define MAX_SESSIONS 8
+
+typedef void (*q6asm_cb) (uint32_t opcode, uint32_t token,
+ void *payload, void *priv);
+struct audio_client;
+struct audio_client *q6asm_audio_client_alloc(struct device *dev,
+ q6asm_cb cb, void *priv,
+ int session_id, int perf_mode);
+void q6asm_audio_client_free(struct audio_client *ac);
+int q6asm_get_session_id(struct audio_client *ac);
+#endif /* __Q6_ASM_H__ */
--
2.16.2
^ permalink raw reply related
* [PATCH v9 05/15] ASoC: qdsp6: q6adm: Add q6adm driver
From: Srinivas Kandagatla @ 2018-05-18 12:56 UTC (permalink / raw)
To: broonie, linux-arm-msm, alsa-devel, bgoswami
Cc: mark.rutland, devicetree, rohkumar, gregkh, plai, lgirdwood,
tiwai, david.brown, robh+dt, Srinivas Kandagatla,
linux-arm-kernel, spatakok, linux-kernel
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds support to Q6ADM (Audio Device Manager) module in
q6dsp. ADM performs routing between audio streams and AFE ports.
It does Rate matching for streams going to devices driven by
different clocks, it handles volume ramping, Mixing with channel
and bit-width. ADM creates and destroys dynamic COPP services
for device-related audio processing as needed.
This patch adds basic support to ADM.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: Rohit kumar <rohitkr@codeaurora.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/Kconfig | 4 +
sound/soc/qcom/qdsp6/Makefile | 1 +
sound/soc/qcom/qdsp6/q6adm.c | 646 ++++++++++++++++++++++++++++++++++++++++++
sound/soc/qcom/qdsp6/q6adm.h | 27 ++
4 files changed, 678 insertions(+)
create mode 100644 sound/soc/qcom/qdsp6/q6adm.c
create mode 100644 sound/soc/qcom/qdsp6/q6adm.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index bb0a2afb0563..971127edbc23 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -50,12 +50,16 @@ config SND_SOC_QDSP6_CORE
config SND_SOC_QDSP6_AFE
tristate
+config SND_SOC_QDSP6_ADM
+ tristate
+
config SND_SOC_QDSP6
tristate "SoC ALSA audio driver for QDSP6"
depends on QCOM_APR && HAS_DMA
select SND_SOC_QDSP6_COMMON
select SND_SOC_QDSP6_CORE
select SND_SOC_QDSP6_AFE
+ select SND_SOC_QDSP6_ADM
help
To add support for MSM QDSP6 Soc Audio.
This will enable sound soc platform specific
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile
index 7ff666bd10ca..95cdb3a12694 100644
--- a/sound/soc/qcom/qdsp6/Makefile
+++ b/sound/soc/qcom/qdsp6/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o
obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o
obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o
+obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o
diff --git a/sound/soc/qcom/qdsp6/q6adm.c b/sound/soc/qcom/qdsp6/q6adm.c
new file mode 100644
index 000000000000..9983c665a941
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6adm.c
@@ -0,0 +1,646 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/jiffies.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/kref.h>
+#include <linux/wait.h>
+#include <linux/soc/qcom/apr.h>
+#include <linux/platform_device.h>
+#include <sound/asound.h>
+#include "q6adm.h"
+#include "q6afe.h"
+#include "q6core.h"
+#include "q6dsp-errno.h"
+#include "q6dsp-common.h"
+
+#define ADM_CMD_DEVICE_OPEN_V5 0x00010326
+#define ADM_CMDRSP_DEVICE_OPEN_V5 0x00010329
+#define ADM_CMD_DEVICE_CLOSE_V5 0x00010327
+#define ADM_CMD_MATRIX_MAP_ROUTINGS_V5 0x00010325
+
+#define TIMEOUT_MS 1000
+#define RESET_COPP_ID 99
+#define INVALID_COPP_ID 0xFF
+/* Definition for a legacy device session. */
+#define ADM_LEGACY_DEVICE_SESSION 0
+#define ADM_MATRIX_ID_AUDIO_RX 0
+#define ADM_MATRIX_ID_AUDIO_TX 1
+
+struct q6copp {
+ int afe_port;
+ int copp_idx;
+ int id;
+ int topology;
+ int mode;
+ int rate;
+ int bit_width;
+ int channels;
+ int app_type;
+ int acdb_id;
+
+ struct aprv2_ibasic_rsp_result_t result;
+ struct kref refcount;
+ wait_queue_head_t wait;
+ struct list_head node;
+ struct q6adm *adm;
+};
+
+struct q6adm {
+ struct apr_device *apr;
+ struct device *dev;
+ struct q6core_svc_api_info ainfo;
+ unsigned long copp_bitmap[AFE_MAX_PORTS];
+ struct list_head copps_list;
+ spinlock_t copps_list_lock;
+ struct aprv2_ibasic_rsp_result_t result;
+ struct mutex lock;
+ wait_queue_head_t matrix_map_wait;
+ struct platform_device *pdev_routing;
+};
+
+struct q6adm_cmd_device_open_v5 {
+ u16 flags;
+ u16 mode_of_operation;
+ u16 endpoint_id_1;
+ u16 endpoint_id_2;
+ u32 topology_id;
+ u16 dev_num_channel;
+ u16 bit_width;
+ u32 sample_rate;
+ u8 dev_channel_mapping[8];
+} __packed;
+
+struct q6adm_cmd_matrix_map_routings_v5 {
+ u32 matrix_id;
+ u32 num_sessions;
+} __packed;
+
+struct q6adm_session_map_node_v5 {
+ u16 session_id;
+ u16 num_copps;
+} __packed;
+
+static struct q6copp *q6adm_find_copp(struct q6adm *adm, int port_idx,
+ int copp_idx)
+{
+ struct q6copp *c = NULL;
+ struct q6copp *ret = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&adm->copps_list_lock, flags);
+ list_for_each_entry(c, &adm->copps_list, node) {
+ if ((port_idx == c->afe_port) && (copp_idx == c->copp_idx)) {
+ ret = c;
+ kref_get(&c->refcount);
+ break;
+ }
+ }
+
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+
+ return ret;
+
+}
+
+static void q6adm_free_copp(struct kref *ref)
+{
+ struct q6copp *c = container_of(ref, struct q6copp, refcount);
+ struct q6adm *adm = c->adm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&adm->copps_list_lock, flags);
+ clear_bit(c->copp_idx, &adm->copp_bitmap[c->afe_port]);
+ list_del(&c->node);
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+ kfree(c);
+}
+
+static int q6adm_callback(struct apr_device *adev, struct apr_resp_pkt *data)
+{
+ struct aprv2_ibasic_rsp_result_t *result = data->payload;
+ int port_idx, copp_idx;
+ struct apr_hdr *hdr = &data->hdr;
+ struct q6copp *copp;
+ struct q6adm *adm = dev_get_drvdata(&adev->dev);
+
+ if (!data->payload_size)
+ return 0;
+
+ copp_idx = (hdr->token) & 0XFF;
+ port_idx = ((hdr->token) >> 16) & 0xFF;
+ if (port_idx < 0 || port_idx >= AFE_MAX_PORTS) {
+ dev_err(&adev->dev, "Invalid port idx %d token %d\n",
+ port_idx, hdr->token);
+ return 0;
+ }
+ if (copp_idx < 0 || copp_idx >= MAX_COPPS_PER_PORT) {
+ dev_err(&adev->dev, "Invalid copp idx %d token %d\n",
+ copp_idx, hdr->token);
+ return 0;
+ }
+
+ switch (hdr->opcode) {
+ case APR_BASIC_RSP_RESULT: {
+ if (result->status != 0) {
+ dev_err(&adev->dev, "cmd = 0x%x return error = 0x%x\n",
+ result->opcode, result->status);
+ }
+ switch (result->opcode) {
+ case ADM_CMD_DEVICE_OPEN_V5:
+ case ADM_CMD_DEVICE_CLOSE_V5:
+ copp = q6adm_find_copp(adm, port_idx, copp_idx);
+ if (!copp)
+ return 0;
+
+ copp->result = *result;
+ wake_up(&copp->wait);
+ kref_put(&copp->refcount, q6adm_free_copp);
+ break;
+ case ADM_CMD_MATRIX_MAP_ROUTINGS_V5:
+ adm->result = *result;
+ wake_up(&adm->matrix_map_wait);
+ break;
+
+ default:
+ dev_err(&adev->dev, "Unknown Cmd: 0x%x\n",
+ result->opcode);
+ break;
+ }
+ return 0;
+ }
+ case ADM_CMDRSP_DEVICE_OPEN_V5: {
+ struct adm_cmd_rsp_device_open_v5 {
+ u32 status;
+ u16 copp_id;
+ u16 reserved;
+ } __packed * open = data->payload;
+
+ copp = q6adm_find_copp(adm, port_idx, copp_idx);
+ if (!copp)
+ return 0;
+
+ if (open->copp_id == INVALID_COPP_ID) {
+ dev_err(&adev->dev, "Invalid coppid rxed %d\n",
+ open->copp_id);
+ copp->result.status = ADSP_EBADPARAM;
+ wake_up(&copp->wait);
+ kref_put(&copp->refcount, q6adm_free_copp);
+ break;
+ }
+ copp->result.opcode = hdr->opcode;
+ copp->id = open->copp_id;
+ wake_up(&copp->wait);
+ kref_put(&copp->refcount, q6adm_free_copp);
+ }
+ break;
+ default:
+ dev_err(&adev->dev, "Unknown cmd:0x%x\n",
+ hdr->opcode);
+ break;
+ }
+
+ return 0;
+}
+
+static struct q6copp *q6adm_alloc_copp(struct q6adm *adm, int port_idx)
+{
+ struct q6copp *c;
+ int idx;
+
+ idx = find_first_zero_bit(&adm->copp_bitmap[port_idx],
+ MAX_COPPS_PER_PORT);
+
+ if (idx > MAX_COPPS_PER_PORT)
+ return ERR_PTR(-EBUSY);
+
+ c = kzalloc(sizeof(*c), GFP_ATOMIC);
+ if (!c)
+ return ERR_PTR(-ENOMEM);
+
+ set_bit(idx, &adm->copp_bitmap[port_idx]);
+ c->copp_idx = idx;
+ c->afe_port = port_idx;
+ c->adm = adm;
+
+ init_waitqueue_head(&c->wait);
+
+ return c;
+}
+
+static int q6adm_apr_send_copp_pkt(struct q6adm *adm, struct q6copp *copp,
+ struct apr_pkt *pkt, uint32_t rsp_opcode)
+{
+ struct device *dev = adm->dev;
+ uint32_t opcode = pkt->hdr.opcode;
+ int ret;
+
+ mutex_lock(&adm->lock);
+ copp->result.opcode = 0;
+ copp->result.status = 0;
+ ret = apr_send_pkt(adm->apr, pkt);
+ if (ret < 0) {
+ dev_err(dev, "Failed to send APR packet\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* Wait for the callback with copp id */
+ if (rsp_opcode)
+ ret = wait_event_timeout(copp->wait,
+ (copp->result.opcode == opcode) ||
+ (copp->result.opcode == rsp_opcode),
+ msecs_to_jiffies(TIMEOUT_MS));
+ else
+ ret = wait_event_timeout(copp->wait,
+ (copp->result.opcode == opcode),
+ msecs_to_jiffies(TIMEOUT_MS));
+
+ if (!ret) {
+ dev_err(dev, "ADM copp cmd timedout\n");
+ ret = -ETIMEDOUT;
+ } else if (copp->result.status > 0) {
+ dev_err(dev, "DSP returned error[%d]\n",
+ copp->result.status);
+ ret = -EINVAL;
+ }
+
+err:
+ mutex_unlock(&adm->lock);
+ return ret;
+}
+
+static int q6adm_device_close(struct q6adm *adm, struct q6copp *copp,
+ int port_id, int copp_idx)
+{
+ struct apr_pkt close;
+
+ close.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ close.hdr.pkt_size = sizeof(close);
+ close.hdr.src_port = port_id;
+ close.hdr.dest_port = copp->id;
+ close.hdr.token = port_id << 16 | copp_idx;
+ close.hdr.opcode = ADM_CMD_DEVICE_CLOSE_V5;
+
+ return q6adm_apr_send_copp_pkt(adm, copp, &close, 0);
+}
+
+static struct q6copp *q6adm_find_matching_copp(struct q6adm *adm,
+ int port_id, int topology,
+ int mode, int rate,
+ int channel_mode, int bit_width,
+ int app_type)
+{
+ struct q6copp *c = NULL;
+ struct q6copp *ret = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&adm->copps_list_lock, flags);
+
+ list_for_each_entry(c, &adm->copps_list, node) {
+ if ((port_id == c->afe_port) && (topology == c->topology) &&
+ (mode == c->mode) && (rate == c->rate) &&
+ (bit_width == c->bit_width) && (app_type == c->app_type)) {
+ ret = c;
+ kref_get(&c->refcount);
+ }
+ }
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+
+ return ret;
+}
+
+static int q6adm_device_open(struct q6adm *adm, struct q6copp *copp,
+ int port_id, int path, int topology,
+ int channel_mode, int bit_width, int rate)
+{
+ struct q6adm_cmd_device_open_v5 *open;
+ int afe_port = q6afe_get_port_id(port_id);
+ struct apr_pkt *pkt;
+ void *p;
+ int ret, pkt_size;
+
+ pkt_size = APR_HDR_SIZE + sizeof(*open);
+ p = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ open = p + APR_HDR_SIZE;
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = afe_port;
+ pkt->hdr.dest_port = afe_port;
+ pkt->hdr.token = port_id << 16 | copp->copp_idx;
+ pkt->hdr.opcode = ADM_CMD_DEVICE_OPEN_V5;
+ open->flags = ADM_LEGACY_DEVICE_SESSION;
+ open->mode_of_operation = path;
+ open->endpoint_id_1 = afe_port;
+ open->topology_id = topology;
+ open->dev_num_channel = channel_mode & 0x00FF;
+ open->bit_width = bit_width;
+ open->sample_rate = rate;
+
+ ret = q6dsp_map_channels(&open->dev_channel_mapping[0],
+ channel_mode);
+ if (ret)
+ goto err;
+
+ ret = q6adm_apr_send_copp_pkt(adm, copp, pkt,
+ ADM_CMDRSP_DEVICE_OPEN_V5);
+
+err:
+ kfree(pkt);
+ return ret;
+}
+
+/**
+ * q6adm_open() - open adm and grab a free copp
+ *
+ * @dev: Pointer to adm child device.
+ * @port_id: port id
+ * @path: playback or capture path.
+ * @rate: rate at which copp is required.
+ * @channel_mode: channel mode
+ * @topology: adm topology id
+ * @perf_mode: performace mode.
+ * @bit_width: audio sample bit width
+ * @app_type: Application type.
+ * @acdb_id: ACDB id
+ *
+ * Return: Will be an negative on error or a valid copp pointer on success.
+ */
+struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate,
+ int channel_mode, int topology, int perf_mode,
+ uint16_t bit_width, int app_type, int acdb_id)
+{
+ struct q6adm *adm = dev_get_drvdata(dev->parent);
+ struct q6copp *copp;
+ unsigned long flags;
+ int ret = 0;
+
+ if (port_id < 0) {
+ dev_err(dev, "Invalid port_id 0x%x\n", port_id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ copp = q6adm_find_matching_copp(adm, port_id, topology, perf_mode,
+ rate, channel_mode, bit_width, app_type);
+ if (copp) {
+ dev_err(dev, "Found Matching Copp 0x%x\n", copp->copp_idx);
+ return copp;
+ }
+
+ spin_lock_irqsave(&adm->copps_list_lock, flags);
+ copp = q6adm_alloc_copp(adm, port_id);
+ if (IS_ERR_OR_NULL(copp)) {
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+ return ERR_CAST(copp);
+ }
+
+ list_add_tail(&copp->node, &adm->copps_list);
+ spin_unlock_irqrestore(&adm->copps_list_lock, flags);
+
+ kref_init(&copp->refcount);
+ copp->topology = topology;
+ copp->mode = perf_mode;
+ copp->rate = rate;
+ copp->channels = channel_mode;
+ copp->bit_width = bit_width;
+ copp->app_type = app_type;
+
+
+ ret = q6adm_device_open(adm, copp, port_id, path, topology,
+ channel_mode, bit_width, rate);
+ if (ret < 0) {
+ kref_put(&copp->refcount, q6adm_free_copp);
+ return ERR_PTR(ret);
+ }
+
+ return copp;
+}
+EXPORT_SYMBOL_GPL(q6adm_open);
+
+/**
+ * q6adm_get_copp_id() - get copp index
+ *
+ * @copp: Pointer to valid copp
+ *
+ * Return: Will be an negative on error or a valid copp index on success.
+ **/
+int q6adm_get_copp_id(struct q6copp *copp)
+{
+ if (!copp)
+ return -EINVAL;
+
+ return copp->copp_idx;
+}
+EXPORT_SYMBOL_GPL(q6adm_get_copp_id);
+
+/**
+ * q6adm_matrix_map() - Map asm streams and afe ports using payload
+ *
+ * @dev: Pointer to adm child device.
+ * @path: playback or capture path.
+ * @payload_map: map between session id and afe ports.
+ * @perf_mode: Performace mode.
+ *
+ * Return: Will be an negative on error or a zero on success.
+ */
+int q6adm_matrix_map(struct device *dev, int path,
+ struct route_payload payload_map, int perf_mode)
+{
+ struct q6adm *adm = dev_get_drvdata(dev->parent);
+ struct q6adm_cmd_matrix_map_routings_v5 *route;
+ struct q6adm_session_map_node_v5 *node;
+ struct apr_pkt *pkt;
+ uint16_t *copps_list;
+ int pkt_size, ret, i, copp_idx;
+ void *matrix_map = NULL;
+ struct q6copp *copp;
+
+ /* Assumes port_ids have already been validated during adm_open */
+ pkt_size = (APR_HDR_SIZE + sizeof(*route) + sizeof(*node) +
+ (sizeof(uint32_t) * payload_map.num_copps));
+
+ matrix_map = kzalloc(pkt_size, GFP_KERNEL);
+ if (!matrix_map)
+ return -ENOMEM;
+
+ pkt = matrix_map;
+ route = matrix_map + APR_HDR_SIZE;
+ node = matrix_map + APR_HDR_SIZE + sizeof(*route);
+ copps_list = matrix_map + APR_HDR_SIZE + sizeof(*route) + sizeof(*node);
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.token = 0;
+ pkt->hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS_V5;
+ route->num_sessions = 1;
+
+ switch (path) {
+ case ADM_PATH_PLAYBACK:
+ route->matrix_id = ADM_MATRIX_ID_AUDIO_RX;
+ break;
+ case ADM_PATH_LIVE_REC:
+ route->matrix_id = ADM_MATRIX_ID_AUDIO_TX;
+ break;
+ default:
+ dev_err(dev, "Wrong path set[%d]\n", path);
+ break;
+ }
+
+ node->session_id = payload_map.session_id;
+ node->num_copps = payload_map.num_copps;
+
+ for (i = 0; i < payload_map.num_copps; i++) {
+ int port_idx = payload_map.port_id[i];
+
+ if (port_idx < 0) {
+ dev_err(dev, "Invalid port_id 0x%x\n",
+ payload_map.port_id[i]);
+ kfree(pkt);
+ return -EINVAL;
+ }
+ copp_idx = payload_map.copp_idx[i];
+
+ copp = q6adm_find_copp(adm, port_idx, copp_idx);
+ if (!copp) {
+ kfree(pkt);
+ return -EINVAL;
+ }
+
+ copps_list[i] = copp->id;
+ kref_put(&copp->refcount, q6adm_free_copp);
+ }
+
+ mutex_lock(&adm->lock);
+ adm->result.status = 0;
+ adm->result.opcode = 0;
+
+ ret = apr_send_pkt(adm->apr, pkt);
+ if (ret < 0) {
+ dev_err(dev, "routing for stream %d failed ret %d\n",
+ payload_map.session_id, ret);
+ goto fail_cmd;
+ }
+ ret = wait_event_timeout(adm->matrix_map_wait,
+ adm->result.opcode == pkt->hdr.opcode,
+ msecs_to_jiffies(TIMEOUT_MS));
+ if (!ret) {
+ dev_err(dev, "routing for stream %d failed\n",
+ payload_map.session_id);
+ ret = -ETIMEDOUT;
+ goto fail_cmd;
+ } else if (adm->result.status > 0) {
+ dev_err(dev, "DSP returned error[%d]\n",
+ adm->result.status);
+ ret = -EINVAL;
+ goto fail_cmd;
+ }
+
+fail_cmd:
+ mutex_unlock(&adm->lock);
+ kfree(pkt);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6adm_matrix_map);
+
+/**
+ * q6adm_close() - Close adm copp
+ *
+ * @dev: Pointer to adm child device.
+ * @copp: pointer to previously opened copp
+ *
+ * Return: Will be an negative on error or a zero on success.
+ */
+int q6adm_close(struct device *dev, struct q6copp *copp)
+{
+ struct q6adm *adm = dev_get_drvdata(dev->parent);
+ int ret = 0;
+
+ ret = q6adm_device_close(adm, copp, copp->afe_port, copp->copp_idx);
+ if (ret < 0) {
+ dev_err(adm->dev, "Failed to close copp %d\n", ret);
+ return ret;
+ }
+
+ kref_put(&copp->refcount, q6adm_free_copp);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6adm_close);
+
+static int q6adm_probe(struct apr_device *adev)
+{
+ struct device *dev = &adev->dev;
+ struct device_node *dais_np;
+ struct q6adm *adm;
+
+ adm = devm_kzalloc(&adev->dev, sizeof(*adm), GFP_KERNEL);
+ if (!adm)
+ return -ENOMEM;
+
+ adm->apr = adev;
+ dev_set_drvdata(&adev->dev, adm);
+ adm->dev = dev;
+ q6core_get_svc_api_info(adev->svc_id, &adm->ainfo);
+ mutex_init(&adm->lock);
+ init_waitqueue_head(&adm->matrix_map_wait);
+
+ INIT_LIST_HEAD(&adm->copps_list);
+ spin_lock_init(&adm->copps_list_lock);
+
+ dais_np = of_get_child_by_name(dev->of_node, "routing");
+ if (dais_np) {
+ adm->pdev_routing = of_platform_device_create(dais_np,
+ "q6routing", dev);
+ of_node_put(dais_np);
+ }
+
+ return 0;
+}
+
+static int q6adm_remove(struct apr_device *adev)
+{
+ struct q6adm *adm = dev_get_drvdata(&adev->dev);
+
+ if (adm->pdev_routing)
+ of_platform_device_destroy(&adm->pdev_routing->dev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id q6adm_device_id[] = {
+ { .compatible = "qcom,q6adm" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6adm_device_id);
+
+static struct apr_driver qcom_q6adm_driver = {
+ .probe = q6adm_probe,
+ .remove = q6adm_remove,
+ .callback = q6adm_callback,
+ .driver = {
+ .name = "qcom-q6adm",
+ .of_match_table = of_match_ptr(q6adm_device_id),
+ },
+};
+
+module_apr_driver(qcom_q6adm_driver);
+MODULE_DESCRIPTION("Q6 Audio Device Manager");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6adm.h b/sound/soc/qcom/qdsp6/q6adm.h
new file mode 100644
index 000000000000..4f56999b7fab
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6adm.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __Q6_ADM_V2_H__
+#define __Q6_ADM_V2_H__
+
+#define ADM_PATH_PLAYBACK 0x1
+#define ADM_PATH_LIVE_REC 0x2
+#define MAX_COPPS_PER_PORT 8
+#define NULL_COPP_TOPOLOGY 0x00010312
+
+/* multiple copp per stream. */
+struct route_payload {
+ int num_copps;
+ int session_id;
+ int copp_idx[MAX_COPPS_PER_PORT];
+ int port_id[MAX_COPPS_PER_PORT];
+};
+
+struct q6copp;
+struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate,
+ int channel_mode, int topology, int perf_mode,
+ uint16_t bit_width, int app_type, int acdb_id);
+int q6adm_close(struct device *dev, struct q6copp *copp);
+int q6adm_get_copp_id(struct q6copp *copp);
+int q6adm_matrix_map(struct device *dev, int path,
+ struct route_payload payload_map, int perf_mode);
+
+#endif /* __Q6_ADM_V2_H__ */
--
2.16.2
^ permalink raw reply related
* [PATCH v9 04/15] ASoC: qdsp6: q6afe: Add support to MI2S sysclks
From: Srinivas Kandagatla @ 2018-05-18 12:55 UTC (permalink / raw)
To: broonie, linux-arm-msm, alsa-devel, bgoswami
Cc: mark.rutland, devicetree, rohkumar, gregkh, plai, lgirdwood,
tiwai, david.brown, robh+dt, Srinivas Kandagatla,
linux-arm-kernel, spatakok, linux-kernel
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds support to LPASS Bit clock, LPASS Digital
core clock and OSR clock. These clocks are required for both
MI2S and PCM setup.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: Rohit kumar <rohitkr@codeaurora.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/qdsp6/q6afe.c | 166 +++++++++++++++++++++++++++++++++++++++++++
sound/soc/qcom/qdsp6/q6afe.h | 131 ++++++++++++++++++++++++++++++++++
2 files changed, 297 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c
index 0004369b3661..de0030068ecb 100644
--- a/sound/soc/qcom/qdsp6/q6afe.c
+++ b/sound/soc/qcom/qdsp6/q6afe.c
@@ -34,6 +34,9 @@
#define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235
+#define AFE_PARAM_ID_LPAIF_CLK_CONFIG 0x00010238
+#define AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG 0x00010239
+
#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212
#define AFE_PARAM_ID_I2S_CONFIG 0x0001020D
@@ -67,6 +70,11 @@
#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E
#define AFE_API_VERSION_SLIMBUS_CONFIG 0x1
+/* Clock set API version */
+#define AFE_API_VERSION_CLOCK_SET 1
+#define Q6AFE_LPASS_CLK_CONFIG_API_VERSION 0x1
+#define AFE_MODULE_CLOCK_SET 0x0001028F
+#define AFE_PARAM_ID_CLOCK_SET 0x00010290
/* SLIMbus Rx port on channel 0. */
#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000
@@ -142,6 +150,13 @@ struct afe_port_param_data_v2 {
u16 reserved;
} __packed;
+struct afe_svc_cmd_set_param {
+ uint32_t payload_size;
+ uint32_t payload_address_lsw;
+ uint32_t payload_address_msw;
+ uint32_t mem_map_handle;
+} __packed;
+
struct afe_port_cmd_set_param_v2 {
u16 port_id;
u16 payload_size;
@@ -202,6 +217,23 @@ struct afe_param_id_slimbus_cfg {
*/
} __packed;
+struct afe_clk_cfg {
+ u32 i2s_cfg_minor_version;
+ u32 clk_val1;
+ u32 clk_val2;
+ u16 clk_src;
+ u16 clk_root;
+ u16 clk_set_mode;
+ u16 reserved;
+} __packed;
+
+struct afe_digital_clk_cfg {
+ u32 i2s_cfg_minor_version;
+ u32 clk_val;
+ u16 clk_root;
+ u16 reserved;
+} __packed;
+
struct afe_param_id_i2s_cfg {
u32 i2s_cfg_minor_version;
u16 bit_width;
@@ -219,6 +251,16 @@ union afe_port_config {
struct afe_param_id_i2s_cfg i2s_cfg;
} __packed;
+
+struct afe_clk_set {
+ uint32_t clk_set_minor_version;
+ uint32_t clk_id;
+ uint32_t clk_freq_in_hz;
+ uint16_t clk_attri;
+ uint16_t clk_root;
+ uint32_t enable;
+};
+
struct q6afe_port {
wait_queue_head_t wait;
union afe_port_config port_cfg;
@@ -404,6 +446,54 @@ static int afe_apr_send_pkt(struct q6afe *afe, struct apr_pkt *pkt,
return ret;
}
+static int q6afe_port_set_param(struct q6afe_port *port, void *data,
+ int param_id, int module_id, int psize)
+{
+ struct afe_svc_cmd_set_param *param;
+ struct afe_port_param_data_v2 *pdata;
+ struct q6afe *afe = port->afe;
+ struct apr_pkt *pkt;
+ u16 port_id = port->id;
+ int ret, pkt_size;
+ void *p, *pl;
+
+ pkt_size = APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata) + psize;
+ p = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ param = p + APR_HDR_SIZE;
+ pdata = p + APR_HDR_SIZE + sizeof(*param);
+ pl = p + APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata);
+ memcpy(pl, data, psize);
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = port->token;
+ pkt->hdr.opcode = AFE_SVC_CMD_SET_PARAM;
+
+ param->payload_size = sizeof(*pdata) + psize;
+ param->payload_address_lsw = 0x00;
+ param->payload_address_msw = 0x00;
+ param->mem_map_handle = 0x00;
+ pdata->module_id = module_id;
+ pdata->param_id = param_id;
+ pdata->param_size = psize;
+
+ ret = afe_apr_send_pkt(afe, pkt, port);
+ if (ret)
+ dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
+ port_id, ret);
+
+ kfree(pkt);
+ return ret;
+}
+
static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data,
int param_id, int module_id, int psize)
{
@@ -453,6 +543,82 @@ static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data,
return ret;
}
+static int q6afe_set_lpass_clock(struct q6afe_port *port,
+ struct afe_clk_cfg *cfg)
+{
+ return q6afe_port_set_param_v2(port, cfg,
+ AFE_PARAM_ID_LPAIF_CLK_CONFIG,
+ AFE_MODULE_AUDIO_DEV_INTERFACE,
+ sizeof(*cfg));
+}
+
+static int q6afe_set_lpass_clock_v2(struct q6afe_port *port,
+ struct afe_clk_set *cfg)
+{
+ return q6afe_port_set_param(port, cfg, AFE_PARAM_ID_CLOCK_SET,
+ AFE_MODULE_CLOCK_SET, sizeof(*cfg));
+}
+
+static int q6afe_set_digital_codec_core_clock(struct q6afe_port *port,
+ struct afe_digital_clk_cfg *cfg)
+{
+ return q6afe_port_set_param_v2(port, cfg,
+ AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG,
+ AFE_MODULE_AUDIO_DEV_INTERFACE,
+ sizeof(*cfg));
+}
+
+int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id,
+ int clk_src, int clk_root,
+ unsigned int freq, int dir)
+{
+ struct afe_clk_cfg ccfg = {0,};
+ struct afe_clk_set cset = {0,};
+ struct afe_digital_clk_cfg dcfg = {0,};
+ int ret;
+
+ switch (clk_id) {
+ case LPAIF_DIG_CLK:
+ dcfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG;
+ dcfg.clk_val = freq;
+ dcfg.clk_root = clk_root;
+ ret = q6afe_set_digital_codec_core_clock(port, &dcfg);
+ break;
+ case LPAIF_BIT_CLK:
+ ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG;
+ ccfg.clk_val1 = freq;
+ ccfg.clk_src = clk_src;
+ ccfg.clk_root = clk_root;
+ ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK1_VALID;
+ ret = q6afe_set_lpass_clock(port, &ccfg);
+ break;
+
+ case LPAIF_OSR_CLK:
+ ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG;
+ ccfg.clk_val2 = freq;
+ ccfg.clk_src = clk_src;
+ ccfg.clk_root = clk_root;
+ ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK2_VALID;
+ ret = q6afe_set_lpass_clock(port, &ccfg);
+ break;
+ case Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT ... Q6AFE_LPASS_CLK_ID_INT_MCLK_1:
+ cset.clk_set_minor_version = AFE_API_VERSION_CLOCK_SET;
+ cset.clk_id = clk_id;
+ cset.clk_freq_in_hz = freq;
+ cset.clk_attri = clk_src;
+ cset.clk_root = clk_root;
+ cset.enable = !!freq;
+ ret = q6afe_set_lpass_clock_v2(port, &cset);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6afe_port_set_sysclk);
+
/**
* q6afe_port_stop() - Stop a afe port
*
diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h
index 3cb3bb4985a9..5ca54a9bdfd5 100644
--- a/sound/soc/qcom/qdsp6/q6afe.h
+++ b/sound/soc/qcom/qdsp6/q6afe.h
@@ -16,6 +16,134 @@
#define AFE_MAX_CHAN_COUNT 8
#define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8
+#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1
+#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0
+
+#define LPAIF_DIG_CLK 1
+#define LPAIF_BIT_CLK 2
+#define LPAIF_OSR_CLK 3
+
+/* Clock ID for Primary I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100
+/* Clock ID for Primary I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_MI2S_EBIT 0x101
+/* Clock ID for Secondary I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT 0x102
+/* Clock ID for Secondary I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_MI2S_EBIT 0x103
+/* Clock ID for Tertiary I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT 0x104
+/* Clock ID for Tertiary I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_MI2S_EBIT 0x105
+/* Clock ID for Quartnery I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT 0x106
+/* Clock ID for Quartnery I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_MI2S_EBIT 0x107
+/* Clock ID for Speaker I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_IBIT 0x108
+/* Clock ID for Speaker I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_EBIT 0x109
+/* Clock ID for Speaker I2S OSR */
+#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_OSR 0x10A
+
+/* Clock ID for QUINARY I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_IBIT 0x10B
+/* Clock ID for QUINARY I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_EBIT 0x10C
+/* Clock ID for SENARY I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_SEN_MI2S_IBIT 0x10D
+/* Clock ID for SENARY I2S EBIT */
+#define Q6AFE_LPASS_CLK_ID_SEN_MI2S_EBIT 0x10E
+/* Clock ID for INT0 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT 0x10F
+/* Clock ID for INT1 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT1_MI2S_IBIT 0x110
+/* Clock ID for INT2 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT2_MI2S_IBIT 0x111
+/* Clock ID for INT3 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT 0x112
+/* Clock ID for INT4 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT4_MI2S_IBIT 0x113
+/* Clock ID for INT5 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT5_MI2S_IBIT 0x114
+/* Clock ID for INT6 I2S IBIT */
+#define Q6AFE_LPASS_CLK_ID_INT6_MI2S_IBIT 0x115
+
+/* Clock ID for QUINARY MI2S OSR CLK */
+#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR 0x116
+
+/* Clock ID for Primary PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_PCM_IBIT 0x200
+/* Clock ID for Primary PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_PCM_EBIT 0x201
+/* Clock ID for Secondary PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_PCM_IBIT 0x202
+/* Clock ID for Secondary PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_PCM_EBIT 0x203
+/* Clock ID for Tertiary PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_PCM_IBIT 0x204
+/* Clock ID for Tertiary PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_PCM_EBIT 0x205
+/* Clock ID for Quartery PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_PCM_IBIT 0x206
+/* Clock ID for Quartery PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_PCM_EBIT 0x207
+/* Clock ID for Quinary PCM IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUIN_PCM_IBIT 0x208
+/* Clock ID for Quinary PCM EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUIN_PCM_EBIT 0x209
+/* Clock ID for QUINARY PCM OSR */
+#define Q6AFE_LPASS_CLK_ID_QUI_PCM_OSR 0x20A
+
+/** Clock ID for Primary TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT 0x200
+/** Clock ID for Primary TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_PRI_TDM_EBIT 0x201
+/** Clock ID for Secondary TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_TDM_IBIT 0x202
+/** Clock ID for Secondary TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_SEC_TDM_EBIT 0x203
+/** Clock ID for Tertiary TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_TDM_IBIT 0x204
+/** Clock ID for Tertiary TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_TER_TDM_EBIT 0x205
+/** Clock ID for Quartery TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT 0x206
+/** Clock ID for Quartery TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUAD_TDM_EBIT 0x207
+/** Clock ID for Quinary TDM IBIT */
+#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_IBIT 0x208
+/** Clock ID for Quinary TDM EBIT */
+#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT 0x209
+/** Clock ID for Quinary TDM OSR */
+#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_OSR 0x20A
+
+/* Clock ID for MCLK1 */
+#define Q6AFE_LPASS_CLK_ID_MCLK_1 0x300
+/* Clock ID for MCLK2 */
+#define Q6AFE_LPASS_CLK_ID_MCLK_2 0x301
+/* Clock ID for MCLK3 */
+#define Q6AFE_LPASS_CLK_ID_MCLK_3 0x302
+/* Clock ID for MCLK4 */
+#define Q6AFE_LPASS_CLK_ID_MCLK_4 0x304
+/* Clock ID for Internal Digital Codec Core */
+#define Q6AFE_LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE 0x303
+/* Clock ID for INT MCLK0 */
+#define Q6AFE_LPASS_CLK_ID_INT_MCLK_0 0x305
+/* Clock ID for INT MCLK1 */
+#define Q6AFE_LPASS_CLK_ID_INT_MCLK_1 0x306
+
+/* Clock attribute for invalid use (reserved for internal usage) */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_INVALID 0x0
+/* Clock attribute for no couple case */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1
+/* Clock attribute for dividend couple case */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_DIVIDEND 0x2
+/* Clock attribute for divisor couple case */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR 0x3
+/* Clock attribute for invert and no couple case */
+#define Q6AFE_LPASS_CLK_ATTRIBUTE_INVERT_COUPLE_NO 0x4
+
struct q6afe_hdmi_cfg {
u16 datatype;
u16 channel_allocation;
@@ -59,4 +187,7 @@ void q6afe_slim_port_prepare(struct q6afe_port *port,
struct q6afe_slim_cfg *cfg);
int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg);
+int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id,
+ int clk_src, int clk_root,
+ unsigned int freq, int dir);
#endif /* __Q6AFE_H__ */
--
2.16.2
^ permalink raw reply related
* [PATCH v9 03/15] ASoC: qdsp6: q6afe: Add support to MI2S ports
From: Srinivas Kandagatla @ 2018-05-18 12:55 UTC (permalink / raw)
To: broonie, linux-arm-msm, alsa-devel, bgoswami
Cc: robh+dt, gregkh, david.brown, mark.rutland, lgirdwood, plai,
tiwai, perex, devicetree, linux-kernel, linux-arm-kernel,
rohkumar, spatakok, Srinivas Kandagatla
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds support to 4 MI2S ports on LPASS.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: Rohit kumar <rohitkr@codeaurora.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/qdsp6/q6afe.c | 224 +++++++++++++++++++++++++++++++++++++++++++
sound/soc/qcom/qdsp6/q6afe.h | 13 +++
2 files changed, 237 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c
index 735c0b5eb55a..0004369b3661 100644
--- a/sound/soc/qcom/qdsp6/q6afe.c
+++ b/sound/soc/qcom/qdsp6/q6afe.c
@@ -15,6 +15,10 @@
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/soc/qcom/apr.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
#include "q6dsp-errno.h"
#include "q6core.h"
#include "q6afe.h"
@@ -31,6 +35,32 @@
#define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235
#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212
+#define AFE_PARAM_ID_I2S_CONFIG 0x0001020D
+
+/* I2S config specific */
+#define AFE_API_VERSION_I2S_CONFIG 0x1
+#define AFE_PORT_I2S_SD0 0x1
+#define AFE_PORT_I2S_SD1 0x2
+#define AFE_PORT_I2S_SD2 0x3
+#define AFE_PORT_I2S_SD3 0x4
+#define AFE_PORT_I2S_SD0_MASK BIT(0x1)
+#define AFE_PORT_I2S_SD1_MASK BIT(0x2)
+#define AFE_PORT_I2S_SD2_MASK BIT(0x3)
+#define AFE_PORT_I2S_SD3_MASK BIT(0x4)
+#define AFE_PORT_I2S_SD0_1_MASK GENMASK(2, 1)
+#define AFE_PORT_I2S_SD2_3_MASK GENMASK(4, 3)
+#define AFE_PORT_I2S_SD0_1_2_MASK GENMASK(3, 1)
+#define AFE_PORT_I2S_SD0_1_2_3_MASK GENMASK(4, 1)
+#define AFE_PORT_I2S_QUAD01 0x5
+#define AFE_PORT_I2S_QUAD23 0x6
+#define AFE_PORT_I2S_6CHS 0x7
+#define AFE_PORT_I2S_8CHS 0x8
+#define AFE_PORT_I2S_MONO 0x0
+#define AFE_PORT_I2S_STEREO 0x1
+#define AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL 0x0
+#define AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL 0x1
+#define AFE_LINEAR_PCM_DATA 0x0
+
/* Port IDs */
#define AFE_API_VERSION_HDMI_CONFIG 0x1
@@ -66,6 +96,19 @@
#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX 0x400c
/* SLIMbus Tx port on channel 6. */
#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX 0x400d
+#define AFE_PORT_ID_PRIMARY_MI2S_RX 0x1000
+#define AFE_PORT_ID_PRIMARY_MI2S_TX 0x1001
+#define AFE_PORT_ID_SECONDARY_MI2S_RX 0x1002
+#define AFE_PORT_ID_SECONDARY_MI2S_TX 0x1003
+#define AFE_PORT_ID_TERTIARY_MI2S_RX 0x1004
+#define AFE_PORT_ID_TERTIARY_MI2S_TX 0x1005
+#define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006
+#define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007
+
+#define Q6AFE_LPASS_MODE_CLK1_VALID 1
+#define Q6AFE_LPASS_MODE_CLK2_VALID 2
+#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1
+#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0
#define TIMEOUT_MS 1000
#define AFE_CMD_RESP_AVAIL 0
@@ -159,10 +202,21 @@ struct afe_param_id_slimbus_cfg {
*/
} __packed;
+struct afe_param_id_i2s_cfg {
+ u32 i2s_cfg_minor_version;
+ u16 bit_width;
+ u16 channel_mode;
+ u16 mono_stereo;
+ u16 ws_src;
+ u32 sample_rate;
+ u16 data_format;
+ u16 reserved;
+} __packed;
union afe_port_config {
struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch;
struct afe_param_id_slimbus_cfg slim_cfg;
+ struct afe_param_id_i2s_cfg i2s_cfg;
} __packed;
struct q6afe_port {
@@ -206,6 +260,22 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = {
SLIMBUS_5_RX, 1, 1},
[SLIMBUS_6_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX,
SLIMBUS_6_RX, 1, 1},
+ [PRIMARY_MI2S_RX] = { AFE_PORT_ID_PRIMARY_MI2S_RX,
+ PRIMARY_MI2S_RX, 1, 1},
+ [PRIMARY_MI2S_TX] = { AFE_PORT_ID_PRIMARY_MI2S_TX,
+ PRIMARY_MI2S_RX, 0, 1},
+ [SECONDARY_MI2S_RX] = { AFE_PORT_ID_SECONDARY_MI2S_RX,
+ SECONDARY_MI2S_RX, 1, 1},
+ [SECONDARY_MI2S_TX] = { AFE_PORT_ID_SECONDARY_MI2S_TX,
+ SECONDARY_MI2S_TX, 0, 1},
+ [TERTIARY_MI2S_RX] = { AFE_PORT_ID_TERTIARY_MI2S_RX,
+ TERTIARY_MI2S_RX, 1, 1},
+ [TERTIARY_MI2S_TX] = { AFE_PORT_ID_TERTIARY_MI2S_TX,
+ TERTIARY_MI2S_TX, 0, 1},
+ [QUATERNARY_MI2S_RX] = { AFE_PORT_ID_QUATERNARY_MI2S_RX,
+ QUATERNARY_MI2S_RX, 1, 1},
+ [QUATERNARY_MI2S_TX] = { AFE_PORT_ID_QUATERNARY_MI2S_TX,
+ QUATERNARY_MI2S_TX, 0, 1},
};
static void q6afe_port_free(struct kref *ref)
@@ -481,6 +551,149 @@ void q6afe_hdmi_port_prepare(struct q6afe_port *port,
}
EXPORT_SYMBOL_GPL(q6afe_hdmi_port_prepare);
+/**
+ * q6afe_i2s_port_prepare() - Prepare i2s afe port.
+ *
+ * @port: Instance of afe port
+ * @cfg: I2S configuration for the afe port
+ * Return: Will be an negative on error and zero on success.
+ */
+int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+ struct device *dev = port->afe->dev;
+ int num_sd_lines;
+
+ pcfg->i2s_cfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG;
+ pcfg->i2s_cfg.sample_rate = cfg->sample_rate;
+ pcfg->i2s_cfg.bit_width = cfg->bit_width;
+ pcfg->i2s_cfg.data_format = AFE_LINEAR_PCM_DATA;
+
+ switch (cfg->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* CPU is slave */
+ pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL;
+ break;
+ default:
+ break;
+ }
+
+ num_sd_lines = hweight_long(cfg->sd_line_mask);
+
+ switch (num_sd_lines) {
+ case 0:
+ dev_err(dev, "no line is assigned\n");
+ return -EINVAL;
+ case 1:
+ switch (cfg->sd_line_mask) {
+ case AFE_PORT_I2S_SD0_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0;
+ break;
+ case AFE_PORT_I2S_SD1_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD1;
+ break;
+ case AFE_PORT_I2S_SD2_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD2;
+ break;
+ case AFE_PORT_I2S_SD3_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD3;
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+ break;
+ case 2:
+ switch (cfg->sd_line_mask) {
+ case AFE_PORT_I2S_SD0_1_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_QUAD01;
+ break;
+ case AFE_PORT_I2S_SD2_3_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_QUAD23;
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+ break;
+ case 3:
+ switch (cfg->sd_line_mask) {
+ case AFE_PORT_I2S_SD0_1_2_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_6CHS;
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+ break;
+ case 4:
+ switch (cfg->sd_line_mask) {
+ case AFE_PORT_I2S_SD0_1_2_3_MASK:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_8CHS;
+
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ dev_err(dev, "Invalid SD lines\n");
+ return -EINVAL;
+ }
+
+ switch (cfg->num_channels) {
+ case 1:
+ case 2:
+ switch (pcfg->i2s_cfg.channel_mode) {
+ case AFE_PORT_I2S_QUAD01:
+ case AFE_PORT_I2S_6CHS:
+ case AFE_PORT_I2S_8CHS:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0;
+ break;
+ case AFE_PORT_I2S_QUAD23:
+ pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD2;
+ break;
+ }
+
+ if (cfg->num_channels == 2)
+ pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_STEREO;
+ else
+ pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_MONO;
+
+ break;
+ case 3:
+ case 4:
+ if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_QUAD01) {
+ dev_err(dev, "Invalid Channel mode\n");
+ return -EINVAL;
+ }
+ break;
+ case 5:
+ case 6:
+ if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_6CHS) {
+ dev_err(dev, "Invalid Channel mode\n");
+ return -EINVAL;
+ }
+ break;
+ case 7:
+ case 8:
+ if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_8CHS) {
+ dev_err(dev, "Invalid Channel mode\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(q6afe_i2s_port_prepare);
+
/**
* q6afe_port_start() - Start a afe port
*
@@ -580,6 +793,17 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id)
case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX:
cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG;
break;
+
+ case AFE_PORT_ID_PRIMARY_MI2S_RX:
+ case AFE_PORT_ID_PRIMARY_MI2S_TX:
+ case AFE_PORT_ID_SECONDARY_MI2S_RX:
+ case AFE_PORT_ID_SECONDARY_MI2S_TX:
+ case AFE_PORT_ID_TERTIARY_MI2S_RX:
+ case AFE_PORT_ID_TERTIARY_MI2S_TX:
+ case AFE_PORT_ID_QUATERNARY_MI2S_RX:
+ case AFE_PORT_ID_QUATERNARY_MI2S_TX:
+ cfg_type = AFE_PARAM_ID_I2S_CONFIG;
+ break;
default:
dev_err(dev, "Invalid port id 0x%x\n", port_id);
return ERR_PTR(-EINVAL);
diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h
index 5659966c6b1e..3cb3bb4985a9 100644
--- a/sound/soc/qcom/qdsp6/q6afe.h
+++ b/sound/soc/qcom/qdsp6/q6afe.h
@@ -11,6 +11,8 @@
#define MSM_AFE_PORT_TYPE_TX 1
#define AFE_MAX_PORTS AFE_PORT_MAX
+#define Q6AFE_MAX_MI2S_LINES 4
+
#define AFE_MAX_CHAN_COUNT 8
#define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8
@@ -29,9 +31,19 @@ struct q6afe_slim_cfg {
u8 ch_mapping[AFE_MAX_CHAN_COUNT];
};
+struct q6afe_i2s_cfg {
+ u32 sample_rate;
+ u16 bit_width;
+ u16 data_format;
+ u16 num_channels;
+ u32 sd_line_mask;
+ int fmt;
+};
+
struct q6afe_port_config {
struct q6afe_hdmi_cfg hdmi;
struct q6afe_slim_cfg slim;
+ struct q6afe_i2s_cfg i2s_cfg;
};
struct q6afe_port;
@@ -45,5 +57,6 @@ void q6afe_hdmi_port_prepare(struct q6afe_port *port,
struct q6afe_hdmi_cfg *cfg);
void q6afe_slim_port_prepare(struct q6afe_port *port,
struct q6afe_slim_cfg *cfg);
+int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg);
#endif /* __Q6AFE_H__ */
--
2.16.2
^ permalink raw reply related
* [PATCH v9 02/15] ASoC: qdsp6: qdafe: Add SLIMBus port Support
From: Srinivas Kandagatla @ 2018-05-18 12:55 UTC (permalink / raw)
To: broonie, linux-arm-msm, alsa-devel, bgoswami
Cc: mark.rutland, devicetree, rohkumar, gregkh, plai, lgirdwood,
tiwai, david.brown, robh+dt, Srinivas Kandagatla,
linux-arm-kernel, spatakok, linux-kernel
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds support to 6 SLIMBus AFE ports, which are used as
backend dais.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: Rohit kumar <rohitkr@codeaurora.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/qdsp6/q6afe.c | 129 +++++++++++++++++++++++++++++++++++++++++++
sound/soc/qcom/qdsp6/q6afe.h | 14 +++++
2 files changed, 143 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c
index 5e0032c13aab..735c0b5eb55a 100644
--- a/sound/soc/qcom/qdsp6/q6afe.c
+++ b/sound/soc/qcom/qdsp6/q6afe.c
@@ -28,9 +28,45 @@
#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210
#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C
+#define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235
+
+#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212
+
/* Port IDs */
#define AFE_API_VERSION_HDMI_CONFIG 0x1
#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E
+
+#define AFE_API_VERSION_SLIMBUS_CONFIG 0x1
+
+/* SLIMbus Rx port on channel 0. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000
+/* SLIMbus Tx port on channel 0. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX 0x4001
+/* SLIMbus Rx port on channel 1. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX 0x4002
+/* SLIMbus Tx port on channel 1. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX 0x4003
+/* SLIMbus Rx port on channel 2. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX 0x4004
+/* SLIMbus Tx port on channel 2. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX 0x4005
+/* SLIMbus Rx port on channel 3. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX 0x4006
+/* SLIMbus Tx port on channel 3. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX 0x4007
+/* SLIMbus Rx port on channel 4. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX 0x4008
+/* SLIMbus Tx port on channel 4. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX 0x4009
+/* SLIMbus Rx port on channel 5. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX 0x400a
+/* SLIMbus Tx port on channel 5. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX 0x400b
+/* SLIMbus Rx port on channel 6. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX 0x400c
+/* SLIMbus Tx port on channel 6. */
+#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX 0x400d
+
#define TIMEOUT_MS 1000
#define AFE_CMD_RESP_AVAIL 0
#define AFE_CMD_RESP_NONE 1
@@ -80,8 +116,53 @@ struct afe_param_id_hdmi_multi_chan_audio_cfg {
u16 reserved;
} __packed;
+struct afe_param_id_slimbus_cfg {
+ u32 sb_cfg_minor_version;
+/* Minor version used for tracking the version of the SLIMBUS
+ * configuration interface.
+ * Supported values: #AFE_API_VERSION_SLIMBUS_CONFIG
+ */
+
+ u16 slimbus_dev_id;
+/* SLIMbus hardware device ID, which is required to handle
+ * multiple SLIMbus hardware blocks.
+ * Supported values: - #AFE_SLIMBUS_DEVICE_1 - #AFE_SLIMBUS_DEVICE_2
+ */
+ u16 bit_width;
+/* Bit width of the sample.
+ * Supported values: 16, 24
+ */
+ u16 data_format;
+/* Data format supported by the SLIMbus hardware. The default is
+ * 0 (#AFE_SB_DATA_FORMAT_NOT_INDICATED), which indicates the
+ * hardware does not perform any format conversions before the data
+ * transfer.
+ */
+ u16 num_channels;
+/* Number of channels.
+ * Supported values: 1 to #AFE_PORT_MAX_AUDIO_CHAN_CNT
+ */
+ u8 shared_ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT];
+/* Mapping of shared channel IDs (128 to 255) to which the
+ * master port is to be connected.
+ * Shared_channel_mapping[i] represents the shared channel assigned
+ * for audio channel i in multichannel audio data.
+ */
+ u32 sample_rate;
+/* Sampling rate of the port.
+ * Supported values:
+ * - #AFE_PORT_SAMPLE_RATE_8K
+ * - #AFE_PORT_SAMPLE_RATE_16K
+ * - #AFE_PORT_SAMPLE_RATE_48K
+ * - #AFE_PORT_SAMPLE_RATE_96K
+ * - #AFE_PORT_SAMPLE_RATE_192K
+ */
+} __packed;
+
+
union afe_port_config {
struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch;
+ struct afe_param_id_slimbus_cfg slim_cfg;
} __packed;
struct q6afe_port {
@@ -111,6 +192,20 @@ struct afe_port_map {
static struct afe_port_map port_maps[AFE_PORT_MAX] = {
[HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX, HDMI_RX, 1, 1},
+ [SLIMBUS_0_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX,
+ SLIMBUS_0_RX, 1, 1},
+ [SLIMBUS_1_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX,
+ SLIMBUS_1_RX, 1, 1},
+ [SLIMBUS_2_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX,
+ SLIMBUS_2_RX, 1, 1},
+ [SLIMBUS_3_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX,
+ SLIMBUS_3_RX, 1, 1},
+ [SLIMBUS_4_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX,
+ SLIMBUS_4_RX, 1, 1},
+ [SLIMBUS_5_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX,
+ SLIMBUS_5_RX, 1, 1},
+ [SLIMBUS_6_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX,
+ SLIMBUS_6_RX, 1, 1},
};
static void q6afe_port_free(struct kref *ref)
@@ -340,6 +435,31 @@ int q6afe_port_stop(struct q6afe_port *port)
}
EXPORT_SYMBOL_GPL(q6afe_port_stop);
+/**
+ * q6afe_slim_port_prepare() - Prepare slim afe port.
+ *
+ * @port: Instance of afe port
+ * @cfg: SLIM configuration for the afe port
+ *
+ */
+void q6afe_slim_port_prepare(struct q6afe_port *port,
+ struct q6afe_slim_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+
+ pcfg->slim_cfg.sb_cfg_minor_version = AFE_API_VERSION_SLIMBUS_CONFIG;
+ pcfg->slim_cfg.sample_rate = cfg->sample_rate;
+ pcfg->slim_cfg.bit_width = cfg->bit_width;
+ pcfg->slim_cfg.num_channels = cfg->num_channels;
+ pcfg->slim_cfg.data_format = cfg->data_format;
+ pcfg->slim_cfg.shared_ch_mapping[0] = cfg->ch_mapping[0];
+ pcfg->slim_cfg.shared_ch_mapping[1] = cfg->ch_mapping[1];
+ pcfg->slim_cfg.shared_ch_mapping[2] = cfg->ch_mapping[2];
+ pcfg->slim_cfg.shared_ch_mapping[3] = cfg->ch_mapping[3];
+
+}
+EXPORT_SYMBOL_GPL(q6afe_slim_port_prepare);
+
/**
* q6afe_hdmi_port_prepare() - Prepare hdmi afe port.
*
@@ -451,6 +571,15 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id)
case AFE_PORT_ID_MULTICHAN_HDMI_RX:
cfg_type = AFE_PARAM_ID_HDMI_CONFIG;
break;
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX:
+ case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX:
+ cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG;
+ break;
default:
dev_err(dev, "Invalid port id 0x%x\n", port_id);
return ERR_PTR(-EINVAL);
diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h
index 3bd991a7c42d..5659966c6b1e 100644
--- a/sound/soc/qcom/qdsp6/q6afe.h
+++ b/sound/soc/qcom/qdsp6/q6afe.h
@@ -11,6 +11,9 @@
#define MSM_AFE_PORT_TYPE_TX 1
#define AFE_MAX_PORTS AFE_PORT_MAX
+#define AFE_MAX_CHAN_COUNT 8
+#define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8
+
struct q6afe_hdmi_cfg {
u16 datatype;
u16 channel_allocation;
@@ -18,8 +21,17 @@ struct q6afe_hdmi_cfg {
u16 bit_width;
};
+struct q6afe_slim_cfg {
+ u32 sample_rate;
+ u16 bit_width;
+ u16 data_format;
+ u16 num_channels;
+ u8 ch_mapping[AFE_MAX_CHAN_COUNT];
+};
+
struct q6afe_port_config {
struct q6afe_hdmi_cfg hdmi;
+ struct q6afe_slim_cfg slim;
};
struct q6afe_port;
@@ -31,5 +43,7 @@ void q6afe_port_put(struct q6afe_port *port);
int q6afe_get_port_id(int index);
void q6afe_hdmi_port_prepare(struct q6afe_port *port,
struct q6afe_hdmi_cfg *cfg);
+void q6afe_slim_port_prepare(struct q6afe_port *port,
+ struct q6afe_slim_cfg *cfg);
#endif /* __Q6AFE_H__ */
--
2.16.2
^ permalink raw reply related
* [PATCH v9 01/15] ASoC: qdsp6: q6afe: Add q6afe driver
From: Srinivas Kandagatla @ 2018-05-18 12:55 UTC (permalink / raw)
To: broonie, linux-arm-msm, alsa-devel, bgoswami
Cc: mark.rutland, devicetree, rohkumar, gregkh, plai, lgirdwood,
tiwai, david.brown, robh+dt, Srinivas Kandagatla,
linux-arm-kernel, spatakok, linux-kernel
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds support to Q6AFE (Audio Front End) module on Q6DSP.
AFE module sits right at the other end of cpu where the codec/audio
devices are connected.
AFE provides abstraced interfaces to both hardware and virtual devices.
Each AFE tx/rx port can be configured to connect to one of the hardware
devices like codec, hdmi, slimbus, i2s and so on. AFE services include
starting, stopping, and if needed, any configurations of the ports.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: Rohit kumar <rohitkr@codeaurora.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/Kconfig | 4 +
sound/soc/qcom/qdsp6/Makefile | 1 +
sound/soc/qcom/qdsp6/q6afe.c | 549 ++++++++++++++++++++++++++++++++++++++++++
sound/soc/qcom/qdsp6/q6afe.h | 35 +++
4 files changed, 589 insertions(+)
create mode 100644 sound/soc/qcom/qdsp6/q6afe.c
create mode 100644 sound/soc/qcom/qdsp6/q6afe.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index 37ee0d958145..bb0a2afb0563 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -47,11 +47,15 @@ config SND_SOC_QDSP6_COMMON
config SND_SOC_QDSP6_CORE
tristate
+config SND_SOC_QDSP6_AFE
+ tristate
+
config SND_SOC_QDSP6
tristate "SoC ALSA audio driver for QDSP6"
depends on QCOM_APR && HAS_DMA
select SND_SOC_QDSP6_COMMON
select SND_SOC_QDSP6_CORE
+ select SND_SOC_QDSP6_AFE
help
To add support for MSM QDSP6 Soc Audio.
This will enable sound soc platform specific
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile
index 03b8e89c9731..7ff666bd10ca 100644
--- a/sound/soc/qcom/qdsp6/Makefile
+++ b/sound/soc/qcom/qdsp6/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o
obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o
+obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o
diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c
new file mode 100644
index 000000000000..5e0032c13aab
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6afe.c
@@ -0,0 +1,549 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/soc/qcom/apr.h>
+#include "q6dsp-errno.h"
+#include "q6core.h"
+#include "q6afe.h"
+
+/* AFE CMDs */
+#define AFE_PORT_CMD_DEVICE_START 0x000100E5
+#define AFE_PORT_CMD_DEVICE_STOP 0x000100E6
+#define AFE_PORT_CMD_SET_PARAM_V2 0x000100EF
+#define AFE_SVC_CMD_SET_PARAM 0x000100f3
+#define AFE_PORT_CMDRSP_GET_PARAM_V2 0x00010106
+#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210
+#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C
+
+/* Port IDs */
+#define AFE_API_VERSION_HDMI_CONFIG 0x1
+#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E
+#define TIMEOUT_MS 1000
+#define AFE_CMD_RESP_AVAIL 0
+#define AFE_CMD_RESP_NONE 1
+
+struct q6afe {
+ struct apr_device *apr;
+ struct device *dev;
+ struct q6core_svc_api_info ainfo;
+ struct mutex lock;
+ struct list_head port_list;
+ spinlock_t port_list_lock;
+ struct platform_device *pdev_dais;
+};
+
+struct afe_port_cmd_device_start {
+ u16 port_id;
+ u16 reserved;
+} __packed;
+
+struct afe_port_cmd_device_stop {
+ u16 port_id;
+ u16 reserved;
+/* Reserved for 32-bit alignment. This field must be set to 0.*/
+} __packed;
+
+struct afe_port_param_data_v2 {
+ u32 module_id;
+ u32 param_id;
+ u16 param_size;
+ u16 reserved;
+} __packed;
+
+struct afe_port_cmd_set_param_v2 {
+ u16 port_id;
+ u16 payload_size;
+ u32 payload_address_lsw;
+ u32 payload_address_msw;
+ u32 mem_map_handle;
+} __packed;
+
+struct afe_param_id_hdmi_multi_chan_audio_cfg {
+ u32 hdmi_cfg_minor_version;
+ u16 datatype;
+ u16 channel_allocation;
+ u32 sample_rate;
+ u16 bit_width;
+ u16 reserved;
+} __packed;
+
+union afe_port_config {
+ struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch;
+} __packed;
+
+struct q6afe_port {
+ wait_queue_head_t wait;
+ union afe_port_config port_cfg;
+ struct aprv2_ibasic_rsp_result_t result;
+ int token;
+ int id;
+ int cfg_type;
+ struct q6afe *afe;
+ struct kref refcount;
+ struct list_head node;
+};
+
+struct afe_port_map {
+ int port_id;
+ int token;
+ int is_rx;
+ int is_dig_pcm;
+};
+
+/*
+ * Mapping between Virtual Port IDs to DSP AFE Port ID
+ * On B Family SoCs DSP Port IDs are consistent across multiple SoCs
+ * on A Family SoCs DSP port IDs are same as virtual Port IDs.
+ */
+
+static struct afe_port_map port_maps[AFE_PORT_MAX] = {
+ [HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX, HDMI_RX, 1, 1},
+};
+
+static void q6afe_port_free(struct kref *ref)
+{
+ struct q6afe_port *port;
+ struct q6afe *afe;
+ unsigned long flags;
+
+ port = container_of(ref, struct q6afe_port, refcount);
+ afe = port->afe;
+ spin_lock_irqsave(&afe->port_list_lock, flags);
+ list_del(&port->node);
+ spin_unlock_irqrestore(&afe->port_list_lock, flags);
+ kfree(port);
+}
+
+static struct q6afe_port *q6afe_find_port(struct q6afe *afe, int token)
+{
+ struct q6afe_port *p = NULL;
+ struct q6afe_port *ret = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&afe->port_list_lock, flags);
+ list_for_each_entry(p, &afe->port_list, node)
+ if (p->token == token) {
+ ret = p;
+ kref_get(&p->refcount);
+ break;
+ }
+
+ spin_unlock_irqrestore(&afe->port_list_lock, flags);
+ return ret;
+}
+
+static int q6afe_callback(struct apr_device *adev, struct apr_resp_pkt *data)
+{
+ struct q6afe *afe = dev_get_drvdata(&adev->dev);
+ struct aprv2_ibasic_rsp_result_t *res;
+ struct apr_hdr *hdr = &data->hdr;
+ struct q6afe_port *port;
+
+ if (!data->payload_size)
+ return 0;
+
+ res = data->payload;
+ switch (hdr->opcode) {
+ case APR_BASIC_RSP_RESULT: {
+ if (res->status) {
+ dev_err(afe->dev, "cmd = 0x%x returned error = 0x%x\n",
+ res->opcode, res->status);
+ }
+ switch (res->opcode) {
+ case AFE_PORT_CMD_SET_PARAM_V2:
+ case AFE_PORT_CMD_DEVICE_STOP:
+ case AFE_PORT_CMD_DEVICE_START:
+ case AFE_SVC_CMD_SET_PARAM:
+ port = q6afe_find_port(afe, hdr->token);
+ if (port) {
+ port->result = *res;
+ wake_up(&port->wait);
+ kref_put(&port->refcount, q6afe_port_free);
+ }
+ break;
+ default:
+ dev_err(afe->dev, "Unknown cmd 0x%x\n", res->opcode);
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * q6afe_get_port_id() - Get port id from a given port index
+ *
+ * @index: port index
+ *
+ * Return: Will be an negative on error or valid port_id on success
+ */
+int q6afe_get_port_id(int index)
+{
+ if (index < 0 || index > AFE_PORT_MAX)
+ return -EINVAL;
+
+ return port_maps[index].port_id;
+}
+EXPORT_SYMBOL_GPL(q6afe_get_port_id);
+
+static int afe_apr_send_pkt(struct q6afe *afe, struct apr_pkt *pkt,
+ struct q6afe_port *port)
+{
+ wait_queue_head_t *wait = &port->wait;
+ struct apr_hdr *hdr = &pkt->hdr;
+ int ret;
+
+ mutex_lock(&afe->lock);
+ port->result.opcode = 0;
+ port->result.status = 0;
+
+ ret = apr_send_pkt(afe->apr, pkt);
+ if (ret < 0) {
+ dev_err(afe->dev, "packet not transmitted (%d)\n", ret);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = wait_event_timeout(*wait, (port->result.opcode == hdr->opcode),
+ msecs_to_jiffies(TIMEOUT_MS));
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ } else if (port->result.status > 0) {
+ dev_err(afe->dev, "DSP returned error[%x]\n",
+ port->result.status);
+ ret = -EINVAL;
+ } else {
+ ret = 0;
+ }
+
+err:
+ mutex_unlock(&afe->lock);
+
+ return ret;
+}
+
+static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data,
+ int param_id, int module_id, int psize)
+{
+ struct afe_port_cmd_set_param_v2 *param;
+ struct afe_port_param_data_v2 *pdata;
+ struct q6afe *afe = port->afe;
+ struct apr_pkt *pkt;
+ u16 port_id = port->id;
+ int ret, pkt_size;
+ void *p, *pl;
+
+ pkt_size = APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata) + psize;
+ p = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ param = p + APR_HDR_SIZE;
+ pdata = p + APR_HDR_SIZE + sizeof(*param);
+ pl = p + APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata);
+ memcpy(pl, data, psize);
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = port->token;
+ pkt->hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2;
+
+ param->port_id = port_id;
+ param->payload_size = sizeof(*pdata) + psize;
+ param->payload_address_lsw = 0x00;
+ param->payload_address_msw = 0x00;
+ param->mem_map_handle = 0x00;
+ pdata->module_id = module_id;
+ pdata->param_id = param_id;
+ pdata->param_size = psize;
+
+ ret = afe_apr_send_pkt(afe, pkt, port);
+ if (ret)
+ dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
+ port_id, ret);
+
+ kfree(pkt);
+ return ret;
+}
+
+/**
+ * q6afe_port_stop() - Stop a afe port
+ *
+ * @port: Instance of port to stop
+ *
+ * Return: Will be an negative on packet size on success.
+ */
+int q6afe_port_stop(struct q6afe_port *port)
+{
+ struct afe_port_cmd_device_stop *stop;
+ struct q6afe *afe = port->afe;
+ struct apr_pkt *pkt;
+ int port_id = port->id;
+ int ret = 0;
+ int index, pkt_size;
+ void *p;
+
+ port_id = port->id;
+ index = port->token;
+ if (index < 0 || index > AFE_PORT_MAX) {
+ dev_err(afe->dev, "AFE port index[%d] invalid!\n", index);
+ return -EINVAL;
+ }
+
+ pkt_size = APR_HDR_SIZE + sizeof(*stop);
+ p = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ stop = p + APR_HDR_SIZE;
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = index;
+ pkt->hdr.opcode = AFE_PORT_CMD_DEVICE_STOP;
+ stop->port_id = port_id;
+ stop->reserved = 0;
+
+ ret = afe_apr_send_pkt(afe, pkt, port);
+ if (ret)
+ dev_err(afe->dev, "AFE close failed %d\n", ret);
+
+ kfree(pkt);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6afe_port_stop);
+
+/**
+ * q6afe_hdmi_port_prepare() - Prepare hdmi afe port.
+ *
+ * @port: Instance of afe port
+ * @cfg: HDMI configuration for the afe port
+ *
+ */
+void q6afe_hdmi_port_prepare(struct q6afe_port *port,
+ struct q6afe_hdmi_cfg *cfg)
+{
+ union afe_port_config *pcfg = &port->port_cfg;
+
+ pcfg->hdmi_multi_ch.hdmi_cfg_minor_version =
+ AFE_API_VERSION_HDMI_CONFIG;
+ pcfg->hdmi_multi_ch.datatype = cfg->datatype;
+ pcfg->hdmi_multi_ch.channel_allocation = cfg->channel_allocation;
+ pcfg->hdmi_multi_ch.sample_rate = cfg->sample_rate;
+ pcfg->hdmi_multi_ch.bit_width = cfg->bit_width;
+}
+EXPORT_SYMBOL_GPL(q6afe_hdmi_port_prepare);
+
+/**
+ * q6afe_port_start() - Start a afe port
+ *
+ * @port: Instance of port to start
+ *
+ * Return: Will be an negative on packet size on success.
+ */
+int q6afe_port_start(struct q6afe_port *port)
+{
+ struct afe_port_cmd_device_start *start;
+ struct q6afe *afe = port->afe;
+ int port_id = port->id;
+ int ret, param_id = port->cfg_type;
+ struct apr_pkt *pkt;
+ int pkt_size;
+ void *p;
+
+ ret = q6afe_port_set_param_v2(port, &port->port_cfg, param_id,
+ AFE_MODULE_AUDIO_DEV_INTERFACE,
+ sizeof(port->port_cfg));
+ if (ret) {
+ dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
+ port_id, ret);
+ return ret;
+ }
+
+ pkt_size = APR_HDR_SIZE + sizeof(*start);
+ p = kzalloc(pkt_size, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ pkt = p;
+ start = p + APR_HDR_SIZE;
+
+ pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+ APR_HDR_LEN(APR_HDR_SIZE),
+ APR_PKT_VER);
+ pkt->hdr.pkt_size = pkt_size;
+ pkt->hdr.src_port = 0;
+ pkt->hdr.dest_port = 0;
+ pkt->hdr.token = port->token;
+ pkt->hdr.opcode = AFE_PORT_CMD_DEVICE_START;
+
+ start->port_id = port_id;
+
+ ret = afe_apr_send_pkt(afe, pkt, port);
+ if (ret)
+ dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
+ port_id, ret);
+
+ kfree(pkt);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(q6afe_port_start);
+
+/**
+ * q6afe_port_get_from_id() - Get port instance from a port id
+ *
+ * @dev: Pointer to afe child device.
+ * @id: port id
+ *
+ * Return: Will be an error pointer on error or a valid afe port
+ * on success.
+ */
+struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id)
+{
+ int port_id;
+ struct q6afe *afe = dev_get_drvdata(dev->parent);
+ struct q6afe_port *port;
+ unsigned long flags;
+ int cfg_type;
+
+ if (id < 0 || id > AFE_PORT_MAX) {
+ dev_err(dev, "AFE port token[%d] invalid!\n", id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* if port is multiple times bind/unbind before callback finishes */
+ port = q6afe_find_port(afe, id);
+ if (port) {
+ dev_err(dev, "AFE Port already open\n");
+ return port;
+ }
+
+ port_id = port_maps[id].port_id;
+
+ switch (port_id) {
+ case AFE_PORT_ID_MULTICHAN_HDMI_RX:
+ cfg_type = AFE_PARAM_ID_HDMI_CONFIG;
+ break;
+ default:
+ dev_err(dev, "Invalid port id 0x%x\n", port_id);
+ return ERR_PTR(-EINVAL);
+ }
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return ERR_PTR(-ENOMEM);
+
+ init_waitqueue_head(&port->wait);
+
+ port->token = id;
+ port->id = port_id;
+ port->afe = afe;
+ port->cfg_type = cfg_type;
+ kref_init(&port->refcount);
+
+ spin_lock_irqsave(&afe->port_list_lock, flags);
+ list_add_tail(&port->node, &afe->port_list);
+ spin_unlock_irqrestore(&afe->port_list_lock, flags);
+
+ return port;
+
+}
+EXPORT_SYMBOL_GPL(q6afe_port_get_from_id);
+
+/**
+ * q6afe_port_put() - Release port reference
+ *
+ * @port: Instance of port to put
+ */
+void q6afe_port_put(struct q6afe_port *port)
+{
+ kref_put(&port->refcount, q6afe_port_free);
+}
+EXPORT_SYMBOL_GPL(q6afe_port_put);
+
+static int q6afe_probe(struct apr_device *adev)
+{
+ struct q6afe *afe;
+ struct device *dev = &adev->dev;
+ struct device_node *dais_np;
+
+ afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL);
+ if (!afe)
+ return -ENOMEM;
+
+ q6core_get_svc_api_info(adev->svc_id, &afe->ainfo);
+ afe->apr = adev;
+ mutex_init(&afe->lock);
+ afe->dev = dev;
+ INIT_LIST_HEAD(&afe->port_list);
+ spin_lock_init(&afe->port_list_lock);
+
+ dev_set_drvdata(dev, afe);
+
+ dais_np = of_get_child_by_name(dev->of_node, "dais");
+ if (dais_np) {
+ afe->pdev_dais = of_platform_device_create(dais_np,
+ "q6afe-dai", dev);
+ of_node_put(dais_np);
+ }
+
+ return 0;
+}
+
+static int q6afe_remove(struct apr_device *adev)
+{
+ struct q6afe *afe = dev_get_drvdata(&adev->dev);
+
+ if (afe->pdev_dais)
+ of_platform_device_destroy(&afe->pdev_dais->dev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id q6afe_device_id[] = {
+ { .compatible = "qcom,q6afe" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6afe_device_id);
+
+static struct apr_driver qcom_q6afe_driver = {
+ .probe = q6afe_probe,
+ .remove = q6afe_remove,
+ .callback = q6afe_callback,
+ .driver = {
+ .name = "qcom-q6afe",
+ .of_match_table = of_match_ptr(q6afe_device_id),
+
+ },
+};
+
+module_apr_driver(qcom_q6afe_driver);
+MODULE_DESCRIPTION("Q6 Audio Front End");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h
new file mode 100644
index 000000000000..3bd991a7c42d
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6afe.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __Q6AFE_H__
+#define __Q6AFE_H__
+
+#include <dt-bindings/sound/qcom,q6afe.h>
+
+#define AFE_PORT_MAX 48
+
+#define MSM_AFE_PORT_TYPE_RX 0
+#define MSM_AFE_PORT_TYPE_TX 1
+#define AFE_MAX_PORTS AFE_PORT_MAX
+
+struct q6afe_hdmi_cfg {
+ u16 datatype;
+ u16 channel_allocation;
+ u32 sample_rate;
+ u16 bit_width;
+};
+
+struct q6afe_port_config {
+ struct q6afe_hdmi_cfg hdmi;
+};
+
+struct q6afe_port;
+
+struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id);
+int q6afe_port_start(struct q6afe_port *port);
+int q6afe_port_stop(struct q6afe_port *port);
+void q6afe_port_put(struct q6afe_port *port);
+int q6afe_get_port_id(int index);
+void q6afe_hdmi_port_prepare(struct q6afe_port *port,
+ struct q6afe_hdmi_cfg *cfg);
+
+#endif /* __Q6AFE_H__ */
--
2.16.2
^ permalink raw reply related
* [PATCH v9 00/15] ASoC: qcom: Add support to QDSP based Audio
From: Srinivas Kandagatla @ 2018-05-18 12:55 UTC (permalink / raw)
To: broonie, linux-arm-msm, alsa-devel, bgoswami
Cc: mark.rutland, devicetree, rohkumar, gregkh, plai, lgirdwood,
tiwai, david.brown, robh+dt, Srinivas Kandagatla,
linux-arm-kernel, spatakok, linux-kernel
Thankyou everyone for providing feedback and testing v8 patchset.
Changes since v8 (https://lkml.org/lkml/2018/5/9/491)
- Fixed usecase across afe, adm, asm where user can trigger
manual unbind if dsp does not respond/responds very lately.
Spotted by Mark B.
- rebased patchset on top of(removed patches arleady applied)
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git/?h=for-next
This patchset aims to provide a basic version of QCOM DSP based
audio support which is available in downstream andriod kernels.
This patchset support audio playback on HDMI-RX, MI2S, SLIMBus and
will add support to other features as we move on.
QDSP has both static and dynamic modules. static modules like AFE
(Audio FrontEnd), ADM (Audio Device Manager), ASM(Audio Stream Manager)
and CORE to provide this audio services.
All these services use APR (Asynchronous Packet Router) protocol
via smd/glink transport to communicate with Application processor.
More details on each module is available in there respective patch.
This patchset is tested on DB820c, with HDMI audio playback, MI2S on
DB410c on top of mainline, Also tested SLIMBus analog audio using
wcd9355 with an additional patches. Patches are also tested on
SDM845 by Rohit.
Here is my test branch incase someone want to try these patches
https://git.linaro.org/people/srinivas.kandagatla/linux.git/log/?h=v4.17-qdsp6
Here is block diagram to give a quick overview of the components
+---------+ +---------+ +---------+
| q6asm | |q6routing| | q6afe |
| dais | <------> | mixers | <-----> | dais |
+---------+ +---------+ +---------+
^ ^ ^
| | |
| +------------------+----------------+ |
| | | | |
v v v v v
+---------+ +---------+ +---------+
| q6ASM | | q6ADM | | q6AFE |
+---------+ +---------+ +---------+
^ ^ ^ ^
| | | CPU Side |
------+---------------------+-------------------+--------
| | |
| |APR(smd/glink) |
| | |
| +------------------+----------------+ |
| | | | |
+-----+--+-----------------------------------+--+-------
| | | | | QDSP Side |
v v v v v v
+---------+ +---------+ +---------+
| ASM | <------> | ADM | <-----> | AFE |
+---------+ +---------+ +---------+
^
|
+-------------------+
|
---------------------------+--------------------------
| Audio I/O |
v v
+--------------------------------------------------+
| Audio devices |
| CODEC | HDMI-TX | PCM | SLIMBUS | I2S |MI2S |...|
| |
+--------------------------------------------------+
Thanks,
srini
Srinivas Kandagatla (15):
ASoC: qdsp6: q6afe: Add q6afe driver
ASoC: qdsp6: qdafe: Add SLIMBus port Support
ASoC: qdsp6: q6afe: Add support to MI2S ports
ASoC: qdsp6: q6afe: Add support to MI2S sysclks
ASoC: qdsp6: q6adm: Add q6adm driver
ASoC: qdsp6: q6asm: Add q6asm driver
ASoC: qdsp6: q6asm: Add support to memory map and unmap
ASoC: qdsp6: q6asm: Add support to audio stream apis
ASoC: qdsp6: q6routing: Add q6routing driver
ASoC: qdsp6: q6routing: Add support to all SLIMBus Mixers
ASoC: qdsp6: q6routing: Add support to MI2S Mixers
ASoC: qdsp6: q6afe: Add q6afe dai driver
ASoC: qdsp6: q6asm: Add q6asm dai driver
ASoC: qdsp6: dt-bindings: Add apq8096 machine bindings
ASoC: qcom: apq8096: Add db820c machine driver
.../devicetree/bindings/sound/qcom,apq8096.txt | 109 ++
sound/soc/qcom/Kconfig | 33 +
sound/soc/qcom/Makefile | 2 +
sound/soc/qcom/apq8096.c | 255 ++++
sound/soc/qcom/qdsp6/Makefile | 6 +
sound/soc/qcom/qdsp6/q6adm.c | 646 +++++++++
sound/soc/qcom/qdsp6/q6adm.h | 27 +
sound/soc/qcom/qdsp6/q6afe-dai.c | 748 +++++++++++
sound/soc/qcom/qdsp6/q6afe.c | 1068 +++++++++++++++
sound/soc/qcom/qdsp6/q6afe.h | 193 +++
sound/soc/qcom/qdsp6/q6asm-dai.c | 624 +++++++++
sound/soc/qcom/qdsp6/q6asm.c | 1399 ++++++++++++++++++++
sound/soc/qcom/qdsp6/q6asm.h | 69 +
sound/soc/qcom/qdsp6/q6routing.c | 990 ++++++++++++++
sound/soc/qcom/qdsp6/q6routing.h | 9 +
15 files changed, 6178 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/qcom,apq8096.txt
create mode 100644 sound/soc/qcom/apq8096.c
create mode 100644 sound/soc/qcom/qdsp6/q6adm.c
create mode 100644 sound/soc/qcom/qdsp6/q6adm.h
create mode 100644 sound/soc/qcom/qdsp6/q6afe-dai.c
create mode 100644 sound/soc/qcom/qdsp6/q6afe.c
create mode 100644 sound/soc/qcom/qdsp6/q6afe.h
create mode 100644 sound/soc/qcom/qdsp6/q6asm-dai.c
create mode 100644 sound/soc/qcom/qdsp6/q6asm.c
create mode 100644 sound/soc/qcom/qdsp6/q6asm.h
create mode 100644 sound/soc/qcom/qdsp6/q6routing.c
create mode 100644 sound/soc/qcom/qdsp6/q6routing.h
--
2.16.2
^ permalink raw reply
* Re: [PATCH V5] ARM: dts: da850-evm: Enable LCD and Backlight
From: Adam Ford @ 2018-05-18 12:50 UTC (permalink / raw)
To: Sekhar Nori; +Cc: devicetree, Adam Ford, arm-soc
In-Reply-To: <2be19e7a-904e-379f-57d9-99e4c557241d@ti.com>
On Fri, May 18, 2018 at 7:37 AM, Sekhar Nori <nsekhar@ti.com> wrote:
> Hi Adam,
>
> On Friday 18 May 2018 06:29 AM, Adam Ford wrote:
>> When using the board files the LCD works, but not with the DT.
>> This adds enables the original da850-evm to work with the same
>> LCD in device tree mode.
>>
>> The EVM has a gpio for the regulator and a PWM for dimming the
>> backlight. The LCD and the vpif display pins are mutually
>> exclusive, so if using the LCD, do not load the vpif driver.
>>
>> Signed-off-by: Adam Ford <aford173@gmail.com>
>
> Looks good mostly, some comments below.
Thanks. I think this is cleaner too.
>
>> ---
>> V5: Resync against v4.18/dt
>>
>> V4: Move the backlight to PWM, so the driver can control the regulator allowing the
>> regulator to power down and enabling the ability to change the brightness of the
>> backlight
>>
>> V3: Fix errant GPIO, label GPIO pins, and rename the regulator to be more explict to
>> backlight which better matches the schematic. Updated the description to explain
>> that it cannot be used at the same time as the vpif driver.
>>
>> V2: Add regulator and GPIO enable pins. Remove PWM backlight and replace with GPIO
>>
>> diff --git a/arch/arm/boot/dts/da850-evm.dts b/arch/arm/boot/dts/da850-evm.dts
>> index 0e82bb988fde..5bf6ea513b12 100644
>> --- a/arch/arm/boot/dts/da850-evm.dts
>> +++ b/arch/arm/boot/dts/da850-evm.dts
>> @@ -27,6 +27,58 @@
>> spi0 = &spi1;
>> };
>>
>> + backlight:backlight-pwm {
>
> Leave a space after the ':' as is the norm.
>
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&ecap2_pins>;
>> + power-supply = <&backlight_reg>;
>> + compatible = "pwm-backlight";
>> + pwms = <&ecap2 0 50000 0>;
>> + brightness-levels = <0 10 20 30 40 50 60 70 80 90 99>;
>> + default-brightness-level = <7>;
>
> Are you able to notice some brightness change at each of these levels?
I am and to change the brightness, but it won't work and pre-released
hardware, because the PWM pins moved around. The schematic needs to
be 1015171 (15 March 2010), Rev A or newer. I don't believe the older
versions were released to the public outside Logic PD or TI, but a lot
of that is before my time. This patch should be the released
hardware.
The changes between the beta release and Rev A include:
Corrected connection of GND_PACK to U32
Connected AXR15 / EPWM0TZ[0] / ECAP2_APWM2 / GP0[7] to J2.99 and U25.46
Added R216-R221, Q10-Q11
Added SPI1_SCS[0] /EPWM1B/GP2[14] /TM64P3_IN12 to J3.36
>
>> + };
>> +
>> + panel {
>> + compatible = "ti,tilcdc,panel";
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&lcd_pins>;
>> + /* The vpif and the LCD are mutually exclusive.
>> + * To enable VPIF, change the status below to 'disabled' then
>> + * then change the status of the vpif below to 'okay' */
>
> Please follow the multi-line comment style described in
> Documentation/process/coding-style.rst
I will look that up. Thanks
>
>> + status = "okay";
>> + enable-gpios = <&gpio 40 GPIO_ACTIVE_HIGH>; /* lcd_panel_pwr */
>> +
>> + panel-info {
>> + ac-bias = <255>;
>> + ac-bias-intrpt = <0>;
>> + dma-burst-sz = <16>;
>> + bpp = <16>;
>> + fdd = <0x80>;
>> + sync-edge = <0>;
>> + sync-ctrl = <1>;
>> + raster-order = <0>;
>> + fifo-th = <0>;
>> + };
>> +
>> + display-timings {
>> + native-mode = <&timing0>;
>> + timing0: 480x272 {
>> + clock-frequency = <9000000>;
>> + hactive = <480>;
>> + vactive = <272>;
>> + hfront-porch = <3>;
>> + hback-porch = <2>;
>> + hsync-len = <42>;
>> + vback-porch = <3>;
>> + vfront-porch = <4>;
>> + vsync-len = <11>;
>> + hsync-active = <0>;
>> + vsync-active = <0>;
>> + de-active = <1>;
>> + pixelclk-active = <1>;
>> + };
>> + };
>> + };
>> +
>> vbat: fixedregulator0 {
>> compatible = "regulator-fixed";
>> regulator-name = "vbat";
>> @@ -35,6 +87,15 @@
>> regulator-boot-on;
>> };
>>
>> + backlight_reg: backlight-regulator {
>
> "backlight_lcd:" perhaps?
Sure
>
>> + compatible = "regulator-fixed";
>> + regulator-name = "lcd_backlight_pwr";
>> + regulator-min-microvolt = <3300000>;
>> + regulator-max-microvolt = <3300000>;
>> + gpio = <&gpio 47 GPIO_ACTIVE_HIGH>; /* lcd_backlight_pwr */
>> + enable-active-high;
>> + };
>> +
>> sound {
>> compatible = "simple-audio-card";
>> simple-audio-card,name = "DA850/OMAP-L138 EVM";
>> @@ -63,6 +124,10 @@
>> };
>> };
>>
>> +&ecap2 {
>> + status = "okay";
>> +};
>> +
>> &pmx_core {
>> status = "okay";
>>
>> @@ -109,6 +174,10 @@
>> status = "okay";
>> };
>>
>> +&lcdc {
>> + status = "okay";
>> +};
>> +
>> &i2c0 {
>> status = "okay";
>> clock-frequency = <100000>;
>> @@ -336,5 +405,8 @@
>> &vpif {
>> pinctrl-names = "default";
>> pinctrl-0 = <&vpif_capture_pins>, <&vpif_display_pins>;
>> - status = "okay";
>> + /* The vpif and the LCD are mutually exclusive.
>> + * To enable VPIF, disable the ti,tilcdc,panel then
>> + * changed the status below to 'okay' */
>
> Here too, please follow the multi-line comment style.
will do.
>
>> + status = "disabled";
>
> Thanks,
> Sekhar
Thank you
adam
^ permalink raw reply
* Re: [PATCH V5] ARM: dts: da850-evm: Enable LCD and Backlight
From: Sekhar Nori @ 2018-05-18 12:37 UTC (permalink / raw)
To: Adam Ford, linux-arm-kernel; +Cc: devicetree, adam.ford
In-Reply-To: <20180518005935.29104-1-aford173@gmail.com>
Hi Adam,
On Friday 18 May 2018 06:29 AM, Adam Ford wrote:
> When using the board files the LCD works, but not with the DT.
> This adds enables the original da850-evm to work with the same
> LCD in device tree mode.
>
> The EVM has a gpio for the regulator and a PWM for dimming the
> backlight. The LCD and the vpif display pins are mutually
> exclusive, so if using the LCD, do not load the vpif driver.
>
> Signed-off-by: Adam Ford <aford173@gmail.com>
Looks good mostly, some comments below.
> ---
> V5: Resync against v4.18/dt
>
> V4: Move the backlight to PWM, so the driver can control the regulator allowing the
> regulator to power down and enabling the ability to change the brightness of the
> backlight
>
> V3: Fix errant GPIO, label GPIO pins, and rename the regulator to be more explict to
> backlight which better matches the schematic. Updated the description to explain
> that it cannot be used at the same time as the vpif driver.
>
> V2: Add regulator and GPIO enable pins. Remove PWM backlight and replace with GPIO
>
> diff --git a/arch/arm/boot/dts/da850-evm.dts b/arch/arm/boot/dts/da850-evm.dts
> index 0e82bb988fde..5bf6ea513b12 100644
> --- a/arch/arm/boot/dts/da850-evm.dts
> +++ b/arch/arm/boot/dts/da850-evm.dts
> @@ -27,6 +27,58 @@
> spi0 = &spi1;
> };
>
> + backlight:backlight-pwm {
Leave a space after the ':' as is the norm.
> + pinctrl-names = "default";
> + pinctrl-0 = <&ecap2_pins>;
> + power-supply = <&backlight_reg>;
> + compatible = "pwm-backlight";
> + pwms = <&ecap2 0 50000 0>;
> + brightness-levels = <0 10 20 30 40 50 60 70 80 90 99>;
> + default-brightness-level = <7>;
Are you able to notice some brightness change at each of these levels?
> + };
> +
> + panel {
> + compatible = "ti,tilcdc,panel";
> + pinctrl-names = "default";
> + pinctrl-0 = <&lcd_pins>;
> + /* The vpif and the LCD are mutually exclusive.
> + * To enable VPIF, change the status below to 'disabled' then
> + * then change the status of the vpif below to 'okay' */
Please follow the multi-line comment style described in
Documentation/process/coding-style.rst
> + status = "okay";
> + enable-gpios = <&gpio 40 GPIO_ACTIVE_HIGH>; /* lcd_panel_pwr */
> +
> + panel-info {
> + ac-bias = <255>;
> + ac-bias-intrpt = <0>;
> + dma-burst-sz = <16>;
> + bpp = <16>;
> + fdd = <0x80>;
> + sync-edge = <0>;
> + sync-ctrl = <1>;
> + raster-order = <0>;
> + fifo-th = <0>;
> + };
> +
> + display-timings {
> + native-mode = <&timing0>;
> + timing0: 480x272 {
> + clock-frequency = <9000000>;
> + hactive = <480>;
> + vactive = <272>;
> + hfront-porch = <3>;
> + hback-porch = <2>;
> + hsync-len = <42>;
> + vback-porch = <3>;
> + vfront-porch = <4>;
> + vsync-len = <11>;
> + hsync-active = <0>;
> + vsync-active = <0>;
> + de-active = <1>;
> + pixelclk-active = <1>;
> + };
> + };
> + };
> +
> vbat: fixedregulator0 {
> compatible = "regulator-fixed";
> regulator-name = "vbat";
> @@ -35,6 +87,15 @@
> regulator-boot-on;
> };
>
> + backlight_reg: backlight-regulator {
"backlight_lcd:" perhaps?
> + compatible = "regulator-fixed";
> + regulator-name = "lcd_backlight_pwr";
> + regulator-min-microvolt = <3300000>;
> + regulator-max-microvolt = <3300000>;
> + gpio = <&gpio 47 GPIO_ACTIVE_HIGH>; /* lcd_backlight_pwr */
> + enable-active-high;
> + };
> +
> sound {
> compatible = "simple-audio-card";
> simple-audio-card,name = "DA850/OMAP-L138 EVM";
> @@ -63,6 +124,10 @@
> };
> };
>
> +&ecap2 {
> + status = "okay";
> +};
> +
> &pmx_core {
> status = "okay";
>
> @@ -109,6 +174,10 @@
> status = "okay";
> };
>
> +&lcdc {
> + status = "okay";
> +};
> +
> &i2c0 {
> status = "okay";
> clock-frequency = <100000>;
> @@ -336,5 +405,8 @@
> &vpif {
> pinctrl-names = "default";
> pinctrl-0 = <&vpif_capture_pins>, <&vpif_display_pins>;
> - status = "okay";
> + /* The vpif and the LCD are mutually exclusive.
> + * To enable VPIF, disable the ti,tilcdc,panel then
> + * changed the status below to 'okay' */
Here too, please follow the multi-line comment style.
> + status = "disabled";
Thanks,
Sekhar
^ permalink raw reply
* Re: [PATCH V6 5/5] remoteproc: qcom: Add q6v5-wcss rproc ops
From: Vinod @ 2018-05-18 12:29 UTC (permalink / raw)
To: Sricharan R
Cc: bjorn.andersson, ohad, robh+dt, mark.rutland, andy.gross,
david.brown, linux-remoteproc, devicetree, linux-kernel,
linux-arm-msm, linux-soc, sibis
In-Reply-To: <1526294812-23390-6-git-send-email-sricharan@codeaurora.org>
On 14-05-18, 16:16, Sricharan R wrote:
> +static int q6v5_wcss_start(struct rproc *rproc)
> +{
> + struct q6v5 *qproc = rproc->priv;
> + int ret = 0;
Superfluous initialization
> +
> + ret = q6v5_clk_enable(qproc->dev, qproc->active_clks,
> + qproc->active_clk_count);
> + if (ret) {
> + dev_err(qproc->dev, "failed to enable clocks\n");
> + return ret;
> + }
> +
> + /* Release Q6 and WCSS reset */
> + ret = reset_control_deassert(qproc->wcss_reset);
> + if (ret)
> + dev_err(qproc->dev, "wcss_reset failed\n");
> +
> + ret = reset_control_deassert(qproc->wcss_q6_reset);
> + if (ret)
> + dev_err(qproc->dev, "wcss_q6_reset failed\n");
shouldn't we abort on these two errors?
> +
> + /* Lithium configuration - clock gating and bus arbitration */
> + ret = regmap_update_bits(qproc->halt_map,
> + qproc->halt_nc + TCSR_GLOBAL_CFG0,
> + 0x1F, 0x14);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(qproc->halt_map,
> + qproc->halt_nc + TCSR_GLOBAL_CFG1,
> + 1, 0);
> + if (ret)
> + return ret;
> +
> + /* Write bootaddr to EVB so that Q6WCSS will jump there after reset */
> + writel(rproc->bootaddr >> 4, qproc->reg_base + QDSP6SS_RST_EVB);
> +
> + ret = q6v5_reset(qproc);
> + if (ret)
> + return ret;
all these returns, aren't we leaving device in some dangling state?
> +static int q6v5_wcss_powerdown(struct q6v5 *qproc)
> +{
> + unsigned int val = 0;
superfluous initialization
> + int ret;
> +
> + /* 1 - Assert WCSS/Q6 HALTREQ */
> + q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_modem);
> +
> + /* 2 - Enable WCSSAON_CONFIG */
> + val = readl(qproc->rmb_base + SSCAON_CONFIG);
> + val |= SSCAON_ENABLE;
> + writel(val, qproc->rmb_base + SSCAON_CONFIG);
> +
> + /* 3 - Set SSCAON_CONFIG */
> + val |= BIT(15);
> + val &= ~BIT(16);
> + val &= ~BIT(17);
> + val &= ~BIT(18);
shouldn't bit 15 thru 18 be defined on what they mean?
> +static int q6v5_q6_powerdown(struct q6v5 *qproc)
> +{
> + int i = 0, ret;
> + unsigned int val = 0;
> +
> + /* 1 - Halt Q6 bus interface */
> + q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_q6);
> +
> + /* 2 - Disable Q6 Core clock */
> + val = readl(qproc->reg_base + QDSP6SS_GFMUX_CTL_REG);
> + val &= ~Q6SS_CLK_ENABLE;
> + writel(val, qproc->reg_base + QDSP6SS_GFMUX_CTL_REG);
> +
> + /* 3 - Clamp I/O */
> + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> + val |= Q6SS_CLAMP_IO;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> +
> + /* 4 - Clamp WL */
> + val |= QDSS_BHS_ON;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> +
> + /* 5 - Clear Erase standby */
> + val &= ~Q6SS_L2DATA_STBY_N;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> +
> + /* 6 - Clear Sleep RTN */
> + val &= ~Q6SS_SLP_RET_N;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> +
> + /* 7 - turn off QDSP6 memory foot/head switch one bank at a time */
> + for (i = 0; i < 20; i++) {
> + val = readl(qproc->reg_base + QDSP6SS_MEM_PWR_CTL);
> + val &= ~BIT(i);
> + writel(val, qproc->reg_base + QDSP6SS_MEM_PWR_CTL);
> + mdelay(1);
> + }
> + /* 8 - Assert QMC memory RTN */
> + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> + val |= QDSP6v56_CLAMP_QMC_MEM;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> +
> + /* 9 - Turn off BHS */
> + val &= ~QDSP6v56_BHS_ON;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> + udelay(1);
> + /* 10 - Wait till BHS Reset is done */
would help in readability if you can be consistent and add empty lines after each
step as done for rest of the routine
> +static int q6v5_wcss_stop(struct rproc *rproc)
> +{
> + struct q6v5 *qproc = rproc->priv;
> + int ret = 0;
this one too, I think if you run with sparse, it should warn you about these
> +
> + qproc->running = false;
> +
> + /* WCSS powerdown */
> + qcom_smem_state_update_bits(qproc->state, BIT(qproc->stop_bit),
> + BIT(qproc->stop_bit));
> +
> + ret = wait_for_completion_timeout(&qproc->stop_done,
> + msecs_to_jiffies(5000));
> + if (ret == 0) {
> + dev_err(qproc->dev, "timed out on wait\n");
> + return -ETIMEDOUT;
> + }
> +
> + qcom_smem_state_update_bits(qproc->state, BIT(qproc->stop_bit), 0);
> +
> + ret = q6v5_wcss_powerdown(qproc);
> + if (ret)
> + return ret;
> +
> + /* Q6 Power down */
> + ret = q6v5_q6_powerdown(qproc);
> + if (ret)
> + return ret;
> +
> + return 0;
this could be optimized to:
return q6v5_q6_powerdown()
--
~Vinod
^ permalink raw reply
* Re: [PATCH V6 3/5] remoteproc: qcom: Split the head and tail of the q5v5-pil rproc reset function
From: Vinod @ 2018-05-18 12:22 UTC (permalink / raw)
To: Sricharan R
Cc: bjorn.andersson, ohad, robh+dt, mark.rutland, andy.gross,
david.brown, linux-remoteproc, devicetree, linux-kernel,
linux-arm-msm, linux-soc, sibis
In-Reply-To: <1526294812-23390-4-git-send-email-sricharan@codeaurora.org>
On 14-05-18, 16:16, Sricharan R wrote:
> +static int q6v5_reset(struct q6v5 *qproc)
> +{
> + u32 ret;
> + int val, i;
> +
> + /* Assert resets, stop core */
> + val = readl(qproc->reg_base + QDSP6SS_RESET_REG);
> + val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE;
> + writel(val, qproc->reg_base + QDSP6SS_RESET_REG);
> +
> + /* BHS require xo cbcr to be enabled */
> + val = readl(qproc->reg_base + QDSP6SS_XO_CBCR);
> + val |= 0x1;
> + writel(val, qproc->reg_base + QDSP6SS_XO_CBCR);
consider adding a updatel macro which does read, update and write for you...
> +
> + /* Read CLKOFF bit to go low indicating CLK is enabled */
> + ret = readl_poll_timeout(qproc->reg_base + QDSP6SS_XO_CBCR,
> + val, !(val & BIT(31)), 1,
> + HALT_CHECK_MAX_LOOPS);
> + if (ret) {
> + dev_err(qproc->dev,
> + "xo cbcr enabling timed out (rc:%d)\n", ret);
> + return ret;
> + }
> + /* Enable power block headswitch and wait for it to stabilize */
> + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> + val |= QDSP6v56_BHS_ON;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> + val |= readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG);
why is this read required
> + udelay(1);
> +
> + /* Put LDO in bypass mode */
> + val |= QDSP6v56_LDO_BYP;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> +
> + /* Deassert QDSP6 compiler memory clamp */
> + val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> + val &= ~QDSP6v56_CLAMP_QMC_MEM;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> +
> + /* Deassert memory peripheral sleep and L2 memory standby */
> + val |= Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N;
> + writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG);
> +
> + /* Turn on L1, L2, ETB and JU memories 1 at a time */
> + val = readl(qproc->reg_base + QDSP6SS_MEM_PWR_CTL);
> + for (i = 19; i >= 0; i--) {
where is the magic number 19 coming from?
--
~Vinod
^ permalink raw reply
* Re: [PATCH V1 2/5] backlight: qcom-wled: Add support for WLED4 peripheral
From: Rob Herring @ 2018-05-18 12:20 UTC (permalink / raw)
To: Kiran Gunda
Cc: Mark Rutland, devicetree, Daniel Thompson,
Bartlomiej Zolnierkiewicz, Jingoo Han,
linux-kernel@vger.kernel.org, dri-devel, Bjorn Andersson,
linux-fbdev, Jacek Anaszewski, Pavel Machek, linux-arm-msm,
Lee Jones, linux-arm-msm-owner, Linux LED Subsystem
In-Reply-To: <7143faaec2d0b7b0fd7e0f3b512839d8@codeaurora.org>
On Thu, May 17, 2018 at 10:10 AM, <kgunda@codeaurora.org> wrote:
> On 2018-05-17 18:01, Rob Herring wrote:
>>
>> On Thu, May 17, 2018 at 4:47 AM, <kgunda@codeaurora.org> wrote:
>>>
>>> On 2018-05-08 15:55, kgunda@codeaurora.org wrote:
>>>>
>>>>
>>>> On 2018-05-07 21:50, Bjorn Andersson wrote:
>>>>>
>>>>>
>>>>> On Thu 03 May 02:57 PDT 2018, Kiran Gunda wrote:
>>>>>
>>>>>> WLED4 peripheral is present on some PMICs like pmi8998
>>>>>> and pm660l. It has a different register map and also
>>>>>> configurations are different. Add support for it.
[...]
>>>>>> + value:
>>>>>> + For pm8941: from 0 to 25000 with 5000 ua step
>>>>>> + Default 20000 uA
>>>>>> + For pmi8998: from 0 to 30000 with 5000 ua step
>>>>>> + Default 25000 uA.
>>>>>
>>>>>
>>>>>
>>>>> These values could be described just as well in mA, so keep the
>>>>> original
>>>>> unit - in particular since the boot-limit is in mA...
>>>>>
>>>> Ok. Will keep the original as is in the next series.
>>>
>>>
>>> Here, I may have to go with the approach as in "qcom,ovp". Because for
>>> pm8941
>>> the current step is 1 mA (I have wrongly mentioned as 5000uA here) and
>>> for
>>> PMI8998
>>> the current step is 2.5 mA. Hence, I will add another variable
>>> "qcom,current-limit-ua"
>>> just like "qcom,ovp-mv".
>>
>>
>> Use unit suffixes defined in bindings/property-units.txt.
>
> Thanks for pointing it ! hope I can use "qcom,current-limit-microamp" and
> "qcom,ovp-millivolt". I am asking this because i found only "-microvolt".
> "-millivolt" is not present in the bindings you pointed.
That's by design so everyone doesn't just pick whatever random units
they like. Does microvolts not give you enough range?
Rob
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel
^ permalink raw reply
* Re: [PATCH 1/3] ARM: dra762: hwmod: Add MCAN support
From: Faiz Abbas @ 2018-05-18 11:54 UTC (permalink / raw)
To: Tony Lindgren
Cc: linux-kernel, linux-omap, devicetree, bcousson, robh+dt,
mark.rutland, paul, lokeshvutla, linux
In-Reply-To: <20180517210931.GQ98604@atomide.com>
Hi Tony,
On Friday 18 May 2018 02:39 AM, Tony Lindgren wrote:
> * Faiz Abbas <faiz_abbas@ti.com> [180408 09:59]:
>> From: Lokesh Vutla <lokeshvutla@ti.com>
>>
>> Add MCAN hwmod data and register it for dra762 silicons.
>>
>> Signed-off-by: Lokesh Vutla <lokeshvutla@ti.com>
>> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com>
>> ---
>> arch/arm/mach-omap2/omap_hwmod_7xx_data.c | 32 +++++++++++++++++++++++++++++++
>> 1 file changed, 32 insertions(+)
>>
>> diff --git a/arch/arm/mach-omap2/omap_hwmod_7xx_data.c b/arch/arm/mach-omap2/omap_hwmod_7xx_data.c
>> index 62352d1..a2cd7f8 100644
>> --- a/arch/arm/mach-omap2/omap_hwmod_7xx_data.c
>> +++ b/arch/arm/mach-omap2/omap_hwmod_7xx_data.c
>> @@ -1356,6 +1356,29 @@ static struct omap_hwmod dra7xx_mailbox13_hwmod = {
>> };
>>
>> /*
>> + * 'mcan' class
>> + *
>> + */
>> +static struct omap_hwmod_class dra76x_mcan_hwmod_class = {
>> + .name = "mcan",
>> +};
>> +
>> +/* mcan */
>> +static struct omap_hwmod dra76x_mcan_hwmod = {
>> + .name = "mcan",
>> + .class = &dra76x_mcan_hwmod_class,
>> + .clkdm_name = "wkupaon_clkdm",
>> + .main_clk = "mcan_clk",
>> + .prcm = {
>> + .omap4 = {
>> + .clkctrl_offs = DRA7XX_CM_WKUPAON_ADC_CLKCTRL_OFFSET,
>> + .context_offs = DRA7XX_RM_WKUPAON_ADC_CONTEXT_OFFSET,
>> + .modulemode = MODULEMODE_SWCTRL,
>> + },
>> + },
>> +};
>
> So based on the ti-sysc related dts comments, this patch should
> work except you should be able to just leave out the clocks here
> with the dts changes.
>
>> +/*
>> * 'mcspi' class
>> *
>> */
>> @@ -3818,6 +3841,14 @@ static struct omap_hwmod_ocp_if dra7xx_l4_per2__epwmss2 = {
>> .user = OCP_USER_MPU,
>> };
>>
>> +/* l3_main_1 -> mcan */
>> +static struct omap_hwmod_ocp_if dra76x_l3_main_1__mcan = {
>> + .master = &dra7xx_l3_main_1_hwmod,
>> + .slave = &dra76x_mcan_hwmod,
>> + .clk = "l3_iclk_div",
>> + .user = OCP_USER_MPU | OCP_USER_SDMA,
>> +};
>> +
>> static struct omap_hwmod_ocp_if *dra7xx_hwmod_ocp_ifs[] __initdata = {
>> &dra7xx_l3_main_1__dmm,
>> &dra7xx_l3_main_2__l3_instr,
>> @@ -3958,6 +3989,7 @@ static struct omap_hwmod_ocp_if *dra7xx_gp_hwmod_ocp_ifs[] __initdata = {
>> /* SoC variant specific hwmod links */
>> static struct omap_hwmod_ocp_if *dra76x_hwmod_ocp_ifs[] __initdata = {
>> &dra7xx_l4_per3__usb_otg_ss4,
>> + &dra76x_l3_main_1__mcan,
>> NULL,
>> };
>
> So the omap_hwmod_class, omap_hwmod_ocp_if and entry on the
> dra76x_hwmod_ocp_ifs list are still needed with ti-sysc for a
> while. Eventually that data will just come from the dts interconnect
> hierarchy. For struct omap_hwmod_class_sysconfig, ti-sysc will
> allocate and it based on the dts data.
>
Thanks for the detailed pointers. I will figure out the ti-sysc node
that needs to be added and post a v2.
Thanks,
Faiz
^ permalink raw reply
* Re: [PATCH v8 2/3] mtd: spi-nor: add rockchip serial flash controller driver
From: Boris Brezillon @ 2018-05-18 11:21 UTC (permalink / raw)
To: Andy Yan
Cc: cyrille.pitchen, mchehab, robh+dt, linux-mtd, shawn.lin, heiko,
devicetree, linux-arm-kernel, linux-kernel, boris.brezillon
In-Reply-To: <1518092327-3827-1-git-send-email-andy.yan@rock-chips.com>
Hi Andy,
Sorry for the late reply.
On Thu, 8 Feb 2018 20:18:47 +0800
Andy Yan <andy.yan@rock-chips.com> wrote:
> From: Shawn Lin <shawn.lin@rock-chips.com>
Commit message please.
>
> Add Rockchip SFC(serial flash controller) driver.
>
> Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
> Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
> Acked-by: Marek Vasut <marek.vasut@gmail.com>
>
> ---
>
> Changes in v8:
> - remove unused macro SFC_CMD_TRAN_BYTES_MASK
> - set max transfer length to 15.5KB
> - remove unnecessary buffer align check
> - remove the duplicate logic what spi-nor.c already does for spi_nor_write
> - add spi_nor_erase, as the SFC should get the erase address.
Would you mind sending a new version addressing the problem reported by
kbuild robots and the comments made by Ezequiel and Robin?
Also, maybe it's too much work, but it would be good to check if the
driver could use the spi_mem interface [1] so that you can move it to
drivers/spi/ and possibly get everything ready for SPI NANDs.
Thanks,
Boris
[1]https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git/tree/include/linux/spi/spi-mem.h?h=for-4.18
^ permalink raw reply
* Re: [PATCH 0/3] Add R8A77980 GEther support
From: Sergei Shtylyov @ 2018-05-18 11:00 UTC (permalink / raw)
To: David Miller; +Cc: netdev, devicetree, robh+dt, mark.rutland, linux-renesas-soc
In-Reply-To: <20180517.145357.2037804925428844092.davem@davemloft.net>
On 05/17/2018 09:53 PM, David Miller wrote:
>> Here's a set of 3 patches against DaveM's 'net-next.git' repo. They (gradually)
>> add R8A77980 GEther support to the 'sh_eth' driver, starting with couple new
>> register bits/values introduced with this chip, and ending with adding a new
>> 'struct sh_eth_cpu_data' instance connected to the new DT "compatible" prop
>> value...
>>
>> [1/1] sh_eth: add RGMII support
>> [2/3] sh_eth: add EDMR.NBST support
>> [3/3] sh_eth: add R8A77980 support
>
> Waiting for a respin of this, correcting the RGMII check in patch #1.
Respun yesterday, will repost RSN. :-)
MBR, Sergei
^ permalink raw reply
* Re: [PATCH 5/9] PM / Domains: dt: Allow power-domain property to be a list of phandles
From: Geert Uytterhoeven @ 2018-05-18 10:46 UTC (permalink / raw)
To: Ulf Hansson
Cc: Rafael J . Wysocki, Linux PM list, Greg Kroah-Hartman, Jon Hunter,
Geert Uytterhoeven, Todor Tomov, Rajendra Nayak, Viresh Kumar,
Vincent Guittot, Kevin Hilman, Linux Kernel Mailing List,
Linux ARM, linux-tegra, Rob Herring,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS
In-Reply-To: <1526639490-12167-6-git-send-email-ulf.hansson@linaro.org>
Hi Ulf,
On Fri, May 18, 2018 at 12:31 PM, Ulf Hansson <ulf.hansson@linaro.org> wrote:
> To be able to describe topologies where devices are partitioned across
> multiple power domains, let's extend the power-domain property to allow
> being a list of phandles.
>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Suggested-by: Jon Hunter <jonathanh@nvidia.com>
> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Thanks for your patch!
> --- a/Documentation/devicetree/bindings/power/power_domain.txt
> +++ b/Documentation/devicetree/bindings/power/power_domain.txt
> @@ -111,8 +111,9 @@ Example 3:
> ==PM domain consumers==
>
> Required properties:
> - - power-domains : A phandle and PM domain specifier as defined by bindings of
> - the power controller specified by phandle.
> + - power-domains : A phandle and PM domain specifier, or a list of phandles, as
A list of PM domain specifiers?
(A PM domain specifier consists of a phandle, and zero or more indices)
> + defined by bindings of the power controller specified by
> + phandle.
>
> Example:
>
> @@ -122,9 +123,16 @@ Example:
> power-domains = <&power 0>;
> };
>
> -The node above defines a typical PM domain consumer device, which is located
> -inside a PM domain with index 0 of a power controller represented by a node
> -with the label "power".
> + leaky-device@12350000 {
I know it's just an example, but this uses the same unit-address and
reg property
as the device node above.
There's a similar issue with the two other examples below, but they
use different
node names (leaky-device0 and leaky-device1).
> + compatible = "foo,i-leak-current";
> + reg = <0x12350000 0x1000>;
> + power-domains = <&power0 0>, <&power1 0> ;
> + };
> +
> +The first example above defines a typical PM domain consumer device, which is
> +located inside a PM domain with index 0 of a power controller represented by a
> +node with the label "power". In the second example the consumer device are
> +partitioned across two PM domains.
>
> Optional properties:
> - required-opps: This contains phandle to an OPP node in another device's OPP
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH] ASoC: ssm2602: Fix ADC powerup sequencing
From: Philipp Zabel @ 2018-05-18 10:46 UTC (permalink / raw)
To: Rob Herring, Marco Felsch
Cc: Mark Rutland, devicetree, Linux-ALSA, Lars-Peter Clausen,
Sascha Hauer, Liam Girdwood, Mark Brown, Sascha Hauer
In-Reply-To: <CAL_JsqKhkVOYf+XEA3MYpK4JYfMrZTbHZtoXgaH=wCBRoU6cfQ@mail.gmail.com>
Hi Rob,
On Thu, 2018-05-17 at 11:14 -0500, Rob Herring wrote:
> On Thu, May 17, 2018 at 8:30 AM, Marco Felsch <m.felsch@pengutronix.de> wrote:
> > From: Philipp Zabel <p.zabel@pengutronix.de>
> >
> > According to the ssm2603 data sheet (control register sequencing), the
> > digital core should be activated only after all necessary bits in the
> > power register are enabled, and a delay determined by the decoupling
> > capacitor on the VMID pin has passed. If the digital core is activated
> > too early, or even before the ADC is powered up, audible artifacts
> > appear at the beginning of the recorded signal.
> >
> > The digital core is also needed for playback, so when recording starts
> > it may already be enabled. This means we cannot get the power sequence
> > correct when we want to be able to start recording after playback.
> >
> > As a workaround put the MIC mute switch into the DAPM routes. This
> > way we can keep the recording disabled until the MIC Bias has settled
> > and thus get rid of audible artifacts.
> >
> > The delay we have to wait depends on a board specific capacitor
> > connected to the VMID pins, so make the delay configurable in the device
> > tree.
> >
> > Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
> > Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
> > Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
> > ---
> > .../devicetree/bindings/sound/adi,ssm2602.txt | 7 +++++
> > sound/soc/codecs/ssm2602.c | 30 +++++++++++++++++--
> > 2 files changed, 35 insertions(+), 2 deletions(-)
> >
> > diff --git a/Documentation/devicetree/bindings/sound/adi,ssm2602.txt b/Documentation/devicetree/bindings/sound/adi,ssm2602.txt
> > index 3b3302fe399b..9334d48c0462 100644
> > --- a/Documentation/devicetree/bindings/sound/adi,ssm2602.txt
> > +++ b/Documentation/devicetree/bindings/sound/adi,ssm2602.txt
> > @@ -11,9 +11,16 @@ Required properties:
> > - reg : the I2C address of the device for I2C, the chip select
> > number for SPI.
> >
> > +Optional properties:
> > +
> > + - startup-delay-us : delay between powering on and activating the digital
> > + core, determined by the decoupling capacitor C on the
> > + VMID pin: delay [µs] = C [µF] * 25000 / 3.5
> > +
>
> We already have similarly defined property. Please reuse that. See mmc
> pwrseq binding.
Do you mean 'post-power-on-delay-ms' from 'mmc-pwseq-simple'?
It is documented as:
- post-power-on-delay-ms : Delay in ms after powering the card and
de-asserting the reset-gpios (if any)
The startup delay here is not after powering the whole IC or deasserting
resets and before it can be used, but after powering up a specific part
of the codec (the ADC) and before unmuting the MIC input to the digital
core during start of decoding. With this in mind, do you still think the
property should be called the same as the mmc full-chip poweron delay?
If so, would it be acceptable to use post-power-on-delay-us to keep the
microsecond resolution?
thanks
Philipp
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
^ permalink raw reply
* Re: [PATCH v5 02/12] media: staging/imx7: add imx7 CSI subdev driver
From: Philipp Zabel @ 2018-05-18 10:33 UTC (permalink / raw)
To: Rui Miguel Silva, mchehab, sakari.ailus, Steve Longerbeam,
Rob Herring
Cc: devel, devicetree, Greg Kroah-Hartman, Ryan Harkin, Fabio Estevam,
Shawn Guo, linux-clk, linux-media
In-Reply-To: <20180518092806.3829-3-rui.silva@linaro.org>
On Fri, 2018-05-18 at 10:27 +0100, Rui Miguel Silva wrote:
> This add the media entity subdevice and control driver for the i.MX7
> CMOS Sensor Interface.
>
> Signed-off-by: Rui Miguel Silva <rui.silva@linaro.org>
> ---
[...]
> +static int imx7_csi_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *node = dev->of_node;
> + struct imx7_csi *csi;
> + struct resource *res;
> + int ret;
> +
> + csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL);
> + if (!csi)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, &csi->sd);
> + csi->dev = dev;
> +
> + ret = imx7_csi_parse_dt(csi);
> + if (ret < 0)
> + return -ENODEV;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + csi->irq = platform_get_irq(pdev, 0);
> + if (!res || csi->irq < 0) {
> + dev_err(dev, "Missing platform resources data\n");
> + return -ENODEV;
> + }
> +
> + csi->regbase = devm_ioremap_resource(dev, res);
> + if (IS_ERR(csi->regbase)) {
> + dev_err(dev, "Failed platform resources map\n");
> + return -ENODEV;
> + }
> +
> + spin_lock_init(&csi->irqlock);
> + mutex_init(&csi->lock);
> +
> + /* install interrupt handler */
> + ret = devm_request_irq(dev, csi->irq, imx7_csi_irq_handler, 0, "csi",
> + (void *)csi);
> + if (ret < 0) {
> + dev_err(dev, "Request CSI IRQ failed.\n");
> + return -ENODEV;
> + }
> +
> + /* add media device */
> + csi->imxmd = imx_media_dev_init(dev, false);
> + if (IS_ERR(csi->imxmd))
> + return PTR_ERR(csi->imxmd);
> +
> + ret = imx_media_of_add_csi(csi->imxmd, node);
> + if (ret < 0)
> + goto media_cleanup;
> +
> + ret = imx_media_dev_notifier_register(csi->imxmd);
> + if (ret < 0)
> + goto media_cleanup;
> +
> + v4l2_subdev_init(&csi->sd, &imx7_csi_subdev_ops);
> + v4l2_set_subdevdata(&csi->sd, csi);
> + csi->sd.internal_ops = &imx7_csi_internal_ops;
> + csi->sd.entity.ops = &imx7_csi_entity_ops;
> + csi->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> + csi->sd.dev = &pdev->dev;
> + csi->sd.owner = THIS_MODULE;
> + csi->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
> + csi->sd.grp_id = IMX_MEDIA_GRP_ID_CSI0;
See my comments on the first patch. Since grp_id specifies an IPU CSI0,
it would be better to use a different id here to make clear this is a
standalone CSI as opposed to an IPU CSI.
regards
Philipp
^ permalink raw reply
* Re: [PATCH v5 01/12] media: staging/imx: refactor imx media device probe
From: Philipp Zabel @ 2018-05-18 10:32 UTC (permalink / raw)
To: Rui Miguel Silva, mchehab, sakari.ailus, Steve Longerbeam,
Rob Herring
Cc: devel, devicetree, Greg Kroah-Hartman, Ryan Harkin, Fabio Estevam,
Shawn Guo, linux-clk, linux-media
In-Reply-To: <20180518092806.3829-2-rui.silva@linaro.org>
Hi Rui,
thank you for refactoring, I think this is much better than having the
pretend capture-subsytem device in the DT.
I would like to get rid of the ipu_present flag, if it can be done
reasonably. For details, see below.
On Fri, 2018-05-18 at 10:27 +0100, Rui Miguel Silva wrote:
> Refactor and move media device initialization code to a new common module, so it
> can be used by other devices, this will allow for example a near to introduce
> imx7 CSI driver, to use this media device.
>
> Also introduce a new flag to control the presence of IPU or not (imx6/5 have
> this but imx7 does not have).
>
> Signed-off-by: Rui Miguel Silva <rui.silva@linaro.org>
> ---
> drivers/staging/media/imx/Makefile | 1 +
> .../staging/media/imx/imx-media-dev-common.c | 102 ++++++++++++++++++
> drivers/staging/media/imx/imx-media-dev.c | 89 ++++-----------
> .../staging/media/imx/imx-media-internal-sd.c | 3 +
> drivers/staging/media/imx/imx-media-of.c | 6 +-
> drivers/staging/media/imx/imx-media.h | 18 ++++
> 6 files changed, 150 insertions(+), 69 deletions(-)
> create mode 100644 drivers/staging/media/imx/imx-media-dev-common.c
>
> diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
> index 698a4210316e..a30b3033f9a3 100644
> --- a/drivers/staging/media/imx/Makefile
> +++ b/drivers/staging/media/imx/Makefile
> @@ -1,5 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
> imx-media-objs := imx-media-dev.o imx-media-internal-sd.o imx-media-of.o
> +imx-media-objs += imx-media-dev-common.o
> imx-media-common-objs := imx-media-utils.o imx-media-fim.o
> imx-media-ic-objs := imx-ic-common.o imx-ic-prp.o imx-ic-prpencvf.o
>
> diff --git a/drivers/staging/media/imx/imx-media-dev-common.c b/drivers/staging/media/imx/imx-media-dev-common.c
> new file mode 100644
> index 000000000000..7e9613ca5b5f
> --- /dev/null
> +++ b/drivers/staging/media/imx/imx-media-dev-common.c
> @@ -0,0 +1,102 @@
> +// SPDX-License-Identifier: GPL
> +/*
> + * V4L2 Media Controller Driver for Freescale common i.MX5/6/7 SOC
> + *
> + * Copyright (c) 2018 Linaro Ltd
> + * Copyright (c) 2016 Mentor Graphics Inc.
> + */
> +
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +#include "imx-media.h"
> +
> +static const struct v4l2_async_notifier_operations imx_media_subdev_ops = {
> + .bound = imx_media_subdev_bound,
> + .complete = imx_media_probe_complete,
> +};
> +
> +static const struct media_device_ops imx_media_md_ops = {
> + .link_notify = imx_media_link_notify,
> +};
> +
> +struct imx_media_dev *imx_media_dev_init(struct device *dev, bool ipu_present)
> +{
> + struct imx_media_dev *imxmd;
> + int ret;
> +
> + imxmd = devm_kzalloc(dev, sizeof(*imxmd), GFP_KERNEL);
> + if (!imxmd)
> + return ERR_PTR(-ENOMEM);
> +
> + dev_set_drvdata(dev, imxmd);
> +
> + strlcpy(imxmd->md.model, "imx-media", sizeof(imxmd->md.model));
> + imxmd->md.ops = &imx_media_md_ops;
> + imxmd->md.dev = dev;
> +
> + imxmd->ipu_present = ipu_present;
Storing this in the imxmd is not necessary if all subdevices are tagged
correctly, see below.
> +
> + mutex_init(&imxmd->mutex);
> +
> + imxmd->v4l2_dev.mdev = &imxmd->md;
> + strlcpy(imxmd->v4l2_dev.name, "imx-media",
> + sizeof(imxmd->v4l2_dev.name));
> +
> + media_device_init(&imxmd->md);
> +
> + ret = v4l2_device_register(dev, &imxmd->v4l2_dev);
> + if (ret < 0) {
> + v4l2_err(&imxmd->v4l2_dev,
> + "Failed to register v4l2_device: %d\n", ret);
> + goto cleanup;
> + }
> +
> + dev_set_drvdata(imxmd->v4l2_dev.dev, imxmd);
> +
> + INIT_LIST_HEAD(&imxmd->vdev_list);
> +
> + return imxmd;
> +
> +cleanup:
> + media_device_cleanup(&imxmd->md);
> +
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(imx_media_dev_init);
> +
> +int imx_media_dev_notifier_register(struct imx_media_dev *imxmd)
> +{
> + int ret;
> +
> + /* no subdevs? just bail */
> + if (imxmd->notifier.num_subdevs == 0) {
> + v4l2_err(&imxmd->v4l2_dev, "no subdevs\n");
> + return -ENODEV;
> + }
> +
> + /* prepare the async subdev notifier and register it */
> + imxmd->notifier.ops = &imx_media_subdev_ops;
> + ret = v4l2_async_notifier_register(&imxmd->v4l2_dev,
> + &imxmd->notifier);
> + if (ret) {
> + v4l2_err(&imxmd->v4l2_dev,
> + "v4l2_async_notifier_register failed with %d\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(imx_media_dev_notifier_register);
> +
> +void imx_media_dev_cleanup(struct imx_media_dev *imxmd)
> +{
> + v4l2_device_unregister(&imxmd->v4l2_dev);
> + media_device_cleanup(&imxmd->md);
> +}
> +EXPORT_SYMBOL_GPL(imx_media_dev_cleanup);
> +
> +void imx_media_dev_notifier_unregister(struct imx_media_dev *imxmd)
> +{
> + v4l2_async_notifier_cleanup(&imxmd->notifier);
> +}
> +EXPORT_SYMBOL_GPL(imx_media_dev_notifier_unregister);
> diff --git a/drivers/staging/media/imx/imx-media-dev.c b/drivers/staging/media/imx/imx-media-dev.c
> index 7e7bd1c6c81b..6160070b72fb 100644
> --- a/drivers/staging/media/imx/imx-media-dev.c
> +++ b/drivers/staging/media/imx/imx-media-dev.c
> @@ -87,6 +87,9 @@ static int imx_media_get_ipu(struct imx_media_dev *imxmd,
> struct ipu_soc *ipu;
> int ipu_id;
>
> + if (!imxmd->ipu_present)
> + return 0;
> +
Instead of returning here right away, it would be better if
imx_media_get_ipu wouldn't be called in the first place for subdevices
that are not an IPU CSI.
The only place this function is called is from imx_media_subdev_bound
iff the sd->grp_id has one of the IMX_MEDIA_GRP_ID_CSI flags set. You
could just make sure that those bits are not set on your CSI subdevice.
Maybe rename them to IMX_MEDIA_GRP_ID_IPU_CSIx to differentiate from the
standalone CSI.
> ipu = dev_get_drvdata(csi_sd->dev->parent);
> if (!ipu) {
> v4l2_err(&imxmd->v4l2_dev,
> @@ -107,9 +110,9 @@ static int imx_media_get_ipu(struct imx_media_dev *imxmd,
> }
>
> /* async subdev bound notifier */
> -static int imx_media_subdev_bound(struct v4l2_async_notifier *notifier,
> - struct v4l2_subdev *sd,
> - struct v4l2_async_subdev *asd)
> +int imx_media_subdev_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *sd,
> + struct v4l2_async_subdev *asd)
> {
> struct imx_media_dev *imxmd = notifier2dev(notifier);
> int ret = 0;
> @@ -293,7 +296,7 @@ static int imx_media_create_pad_vdev_lists(struct imx_media_dev *imxmd)
> }
>
> /* async subdev complete notifier */
> -static int imx_media_probe_complete(struct v4l2_async_notifier *notifier)
> +int imx_media_probe_complete(struct v4l2_async_notifier *notifier)
> {
> struct imx_media_dev *imxmd = notifier2dev(notifier);
> int ret;
> @@ -317,11 +320,6 @@ static int imx_media_probe_complete(struct v4l2_async_notifier *notifier)
> return media_device_register(&imxmd->md);
> }
>
> -static const struct v4l2_async_notifier_operations imx_media_subdev_ops = {
> - .bound = imx_media_subdev_bound,
> - .complete = imx_media_probe_complete,
> -};
> -
> /*
> * adds controls to a video device from an entity subdevice.
> * Continues upstream from the entity's sink pads.
> @@ -365,8 +363,8 @@ static int imx_media_inherit_controls(struct imx_media_dev *imxmd,
> return ret;
> }
>
> -static int imx_media_link_notify(struct media_link *link, u32 flags,
> - unsigned int notification)
> +int imx_media_link_notify(struct media_link *link, u32 flags,
> + unsigned int notification)
> {
> struct media_entity *source = link->source->entity;
> struct imx_media_pad_vdev *pad_vdev;
> @@ -429,10 +427,6 @@ static int imx_media_link_notify(struct media_link *link, u32 flags,
> return ret;
> }
>
> -static const struct media_device_ops imx_media_md_ops = {
> - .link_notify = imx_media_link_notify,
> -};
> -
> static int imx_media_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> @@ -440,74 +434,36 @@ static int imx_media_probe(struct platform_device *pdev)
> struct imx_media_dev *imxmd;
> int ret;
>
> - imxmd = devm_kzalloc(dev, sizeof(*imxmd), GFP_KERNEL);
> - if (!imxmd)
> - return -ENOMEM;
> -
> - dev_set_drvdata(dev, imxmd);
> -
> - strlcpy(imxmd->md.model, "imx-media", sizeof(imxmd->md.model));
> - imxmd->md.ops = &imx_media_md_ops;
> - imxmd->md.dev = dev;
> -
> - mutex_init(&imxmd->mutex);
> -
> - imxmd->v4l2_dev.mdev = &imxmd->md;
> - strlcpy(imxmd->v4l2_dev.name, "imx-media",
> - sizeof(imxmd->v4l2_dev.name));
> -
> - media_device_init(&imxmd->md);
> -
> - ret = v4l2_device_register(dev, &imxmd->v4l2_dev);
> - if (ret < 0) {
> - v4l2_err(&imxmd->v4l2_dev,
> - "Failed to register v4l2_device: %d\n", ret);
> - goto cleanup;
> - }
> -
> - dev_set_drvdata(imxmd->v4l2_dev.dev, imxmd);
> -
> - INIT_LIST_HEAD(&imxmd->vdev_list);
> + imxmd = imx_media_dev_init(dev, true);
> + if (IS_ERR(imxmd))
> + return PTR_ERR(imxmd);
>
> ret = imx_media_add_of_subdevs(imxmd, node);
> if (ret) {
> v4l2_err(&imxmd->v4l2_dev,
> "add_of_subdevs failed with %d\n", ret);
> - goto notifier_cleanup;
> + goto dev_cleanup;
> }
>
> ret = imx_media_add_internal_subdevs(imxmd);
> if (ret) {
> v4l2_err(&imxmd->v4l2_dev,
> "add_internal_subdevs failed with %d\n", ret);
> - goto notifier_cleanup;
> - }
> -
> - /* no subdevs? just bail */
> - if (imxmd->notifier.num_subdevs == 0) {
> - ret = -ENODEV;
> - goto notifier_cleanup;
> + goto dev_cleanup;
> }
>
> - /* prepare the async subdev notifier and register it */
> - imxmd->notifier.ops = &imx_media_subdev_ops;
> - ret = v4l2_async_notifier_register(&imxmd->v4l2_dev,
> - &imxmd->notifier);
> - if (ret) {
> - v4l2_err(&imxmd->v4l2_dev,
> - "v4l2_async_notifier_register failed with %d\n", ret);
> + ret = imx_media_dev_notifier_register(imxmd);
> + if (ret < 0)
> goto del_int;
> - }
>
> return 0;
>
> del_int:
> imx_media_remove_internal_subdevs(imxmd);
> -notifier_cleanup:
> - v4l2_async_notifier_cleanup(&imxmd->notifier);
> - v4l2_device_unregister(&imxmd->v4l2_dev);
> -cleanup:
> - media_device_cleanup(&imxmd->md);
> +
> +dev_cleanup:
> + imx_media_dev_cleanup(imxmd);
> +
> return ret;
> }
>
> @@ -520,10 +476,9 @@ static int imx_media_remove(struct platform_device *pdev)
>
> v4l2_async_notifier_unregister(&imxmd->notifier);
> imx_media_remove_internal_subdevs(imxmd);
> - v4l2_async_notifier_cleanup(&imxmd->notifier);
> - v4l2_device_unregister(&imxmd->v4l2_dev);
> + imx_media_dev_notifier_unregister(imxmd);
> media_device_unregister(&imxmd->md);
> - media_device_cleanup(&imxmd->md);
> + imx_media_dev_cleanup(imxmd);
>
> return 0;
> }
> diff --git a/drivers/staging/media/imx/imx-media-internal-sd.c b/drivers/staging/media/imx/imx-media-internal-sd.c
> index 0fdc45dbfb76..2bcdc232369a 100644
> --- a/drivers/staging/media/imx/imx-media-internal-sd.c
> +++ b/drivers/staging/media/imx/imx-media-internal-sd.c
> @@ -238,6 +238,9 @@ int imx_media_create_internal_links(struct imx_media_dev *imxmd,
> struct media_pad *pad;
> int i, j, ret;
>
> + if (!imxmd->ipu_present)
> + return 0;
> +
Same here. Just make sure imx_media_create_internal_links is never
called. Possibly rename to imx_media_create_ipu_internal_links. It is
only called from imx_media_create_links, and only for subdevices that
have one of the following sd->grp_id set:
IMX_MEDIA_GRP_ID_VDIC
IMX_MEDIA_GRP_ID_IC_PRP
IMX_MEDIA_GRP_ID_IC_PRPENC
IMX_MEDIA_GRP_ID_IC_PRPVF
IMX_MEDIA_GRP_ID_CSI0
IMX_MEDIA_GRP_ID_CSI1
Just make sure your subdevices use neither of these. I would be fine
with adding an IPU_ prefix to all of them.
regards
Philipp
_______________________________________________
devel mailing list
devel@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel
^ permalink raw reply
* [PATCH 6/9] PM / Domains: Don't attach devices in genpd with multi PM domains
From: Ulf Hansson @ 2018-05-18 10:31 UTC (permalink / raw)
To: Rafael J . Wysocki, linux-pm
Cc: Ulf Hansson, Greg Kroah-Hartman, Jon Hunter, Geert Uytterhoeven,
Todor Tomov, Rajendra Nayak, Viresh Kumar, Vincent Guittot,
Kevin Hilman, linux-kernel, linux-arm-kernel, linux-tegra,
Rob Herring, devicetree
In-Reply-To: <1526639490-12167-1-git-send-email-ulf.hansson@linaro.org>
The power-domain DT property may now contain a list of phandles, which
represents that a device are partitioned across multiple PM domains. This
leads to a new situation in genpd_dev_pm_attach(), as only one PM domain
can be attached per device.
To remain things simple for the most common configuration, when a single PM
domain is used, let's treat the multiple PM domain case as being specific.
In other words, let's change genpd_dev_pm_attach() to check for multiple PM
domains and prevent it from attach any PM domain for this case. Instead,
leave this to be managed separately, from following changes to genpd.
Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Suggested-by: Jon Hunter <jonathanh@nvidia.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
---
drivers/base/power/domain.c | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index 050ce07..4597e1c 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -2229,10 +2229,10 @@ static void genpd_dev_pm_sync(struct device *dev)
* attaches the device to retrieved pm_domain ops.
*
* Returns 1 on successfully attached PM domain, 0 when the device don't need a
- * PM domain or a negative error code in case of failures. Note that if a
- * power-domain exists for the device, but it cannot be found or turned on,
- * then return -EPROBE_DEFER to ensure that the device is not probed and to
- * re-try again later.
+ * PM domain or when multiple power-domains exists for it, else a negative error
+ * code. Note that if a power-domain exists for the device, but it cannot be
+ * found or turned on, then return -EPROBE_DEFER to ensure that the device is
+ * not probed and to re-try again later.
*/
int genpd_dev_pm_attach(struct device *dev)
{
@@ -2243,10 +2243,18 @@ int genpd_dev_pm_attach(struct device *dev)
if (!dev->of_node)
return 0;
+ /*
+ * Devices with multiple PM domains must be attached separately, as we
+ * can only attach one PM domain per device.
+ */
+ if (of_count_phandle_with_args(dev->of_node, "power-domains",
+ "#power-domain-cells") != 1)
+ return 0;
+
ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
"#power-domain-cells", 0, &pd_args);
if (ret < 0)
- return 0;
+ return ret;
mutex_lock(&gpd_list_lock);
pd = genpd_get_from_provider(&pd_args);
--
2.7.4
^ permalink raw reply related
* [PATCH 5/9] PM / Domains: dt: Allow power-domain property to be a list of phandles
From: Ulf Hansson @ 2018-05-18 10:31 UTC (permalink / raw)
To: Rafael J . Wysocki, linux-pm
Cc: Ulf Hansson, Greg Kroah-Hartman, Jon Hunter, Geert Uytterhoeven,
Todor Tomov, Rajendra Nayak, Viresh Kumar, Vincent Guittot,
Kevin Hilman, linux-kernel, linux-arm-kernel, linux-tegra,
Rob Herring, devicetree
In-Reply-To: <1526639490-12167-1-git-send-email-ulf.hansson@linaro.org>
To be able to describe topologies where devices are partitioned across
multiple power domains, let's extend the power-domain property to allow
being a list of phandles.
Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Suggested-by: Jon Hunter <jonathanh@nvidia.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
---
.../devicetree/bindings/power/power_domain.txt | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/power/power_domain.txt b/Documentation/devicetree/bindings/power/power_domain.txt
index 4733f76..b27ae7f 100644
--- a/Documentation/devicetree/bindings/power/power_domain.txt
+++ b/Documentation/devicetree/bindings/power/power_domain.txt
@@ -111,8 +111,9 @@ Example 3:
==PM domain consumers==
Required properties:
- - power-domains : A phandle and PM domain specifier as defined by bindings of
- the power controller specified by phandle.
+ - power-domains : A phandle and PM domain specifier, or a list of phandles, as
+ defined by bindings of the power controller specified by
+ phandle.
Example:
@@ -122,9 +123,16 @@ Example:
power-domains = <&power 0>;
};
-The node above defines a typical PM domain consumer device, which is located
-inside a PM domain with index 0 of a power controller represented by a node
-with the label "power".
+ leaky-device@12350000 {
+ compatible = "foo,i-leak-current";
+ reg = <0x12350000 0x1000>;
+ power-domains = <&power0 0>, <&power1 0> ;
+ };
+
+The first example above defines a typical PM domain consumer device, which is
+located inside a PM domain with index 0 of a power controller represented by a
+node with the label "power". In the second example the consumer device are
+partitioned across two PM domains.
Optional properties:
- required-opps: This contains phandle to an OPP node in another device's OPP
--
2.7.4
^ permalink raw reply related
* Re: [PATCH v11 0/4] iommu/arm-smmu: Add runtime pm/sleep support
From: Vivek Gautam @ 2018-05-18 10:14 UTC (permalink / raw)
To: Robin Murphy, Will Deacon
Cc: Mark Rutland, devicetree-u79uwXL29TY76Z2rM5mHXA, Lukas Wunner,
Stephen Boyd, Rafael J. Wysocki, Linux Kernel Mailing List,
iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA, Rob Herring,
linux-arm-msm
In-Reply-To: <CAAFQd5CDY5p8oBz4QC5f6GsKiiYqdenCHGCqKhuWn10JwKjOpw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
Hi Robin,
On 4/9/2018 9:52 AM, Tomasz Figa wrote:
> Hi Will, Robin,
>
> On Thu, Mar 22, 2018 at 7:22 PM Vivek Gautam <vivek.gautam-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
> wrote:
>
>> This series provides the support for turning on the arm-smmu's
>> clocks/power domains using runtime pm. This is done using the
>> recently introduced device links patches, which lets the smmu's
>> runtime to follow the master's runtime pm, so the smmu remains
>> powered only when the masters use it.
>> As not all implementations support clock/power gating, we are checking
>> for a valid 'smmu->dev's pm_domain' to conditionally enable the runtime
>> power management for such smmu implementations that can support it.
>> This series also adds support for Qcom's arm-smmu-v2 variant that
>> has different clocks and power requirements.
>> Took some reference from the exynos runtime patches [1].
>> With conditional runtime pm now, we avoid touching dev->power.lock
>> in fastpaths for smmu implementations that don't need to do anything
>> useful with pm_runtime.
>> This lets us to use the much-argued pm_runtime_get_sync/put_sync()
>> calls in map/unmap callbacks so that the clients do not have to
>> worry about handling any of the arm-smmu's power.
>> Previous version of this patch series is @ [5].
>> [v11]
>> * Some more cleanups for device link. We don't need an explicit
>> delete for device link from the driver, but just set the flag
>> DL_FLAG_AUTOREMOVE.
>> device_link_add() API description says -
>> "If the DL_FLAG_AUTOREMOVE is set, the link will be removed
>> automatically when the consumer device driver unbinds."
>> * Addressed the comments for 'smmu' in arm_smmu_map/unmap().
>> * Dropped the patch [10] that introduced device_link_del_dev() API.
> As far as I can see, this version addresses all the earlier comments. Do
> you think this is something that you could apply?
>
> Best regards,
> Tomasz
Gentle ping. Are we picking this series for 4.18?
Regards
Vivek
^ permalink raw reply
* RE: [v4 2/6] dmaengine: fsl-qdma: Add qDMA controller driver for Layerscape SoCs
From: Wen He @ 2018-05-18 10:04 UTC (permalink / raw)
To: Vinod
Cc: dmaengine@vger.kernel.org, robh+dt@kernel.org,
devicetree@vger.kernel.org, Leo Li, Jiafei Pan, Jiaheng Fan
In-Reply-To: <20180518042125.GE2932@vkoul-mobl>
> -----Original Message-----
> From: dmaengine-owner@vger.kernel.org
> [mailto:dmaengine-owner@vger.kernel.org] On Behalf Of Vinod
> Sent: 2018年5月18日 12:21
> To: Wen He <wen.he_1@nxp.com>
> Cc: dmaengine@vger.kernel.org; robh+dt@kernel.org;
> devicetree@vger.kernel.org; Leo Li <leoyang.li@nxp.com>; Jiafei Pan
> <jiafei.pan@nxp.com>; Jiaheng Fan <jiaheng.fan@nxp.com>
> Subject: Re: [v4 2/6] dmaengine: fsl-qdma: Add qDMA controller driver for
> Layerscape SoCs
>
> On 17-05-18, 11:27, Wen He wrote:
> > > > +
> > > > +/* Registers for bit and genmask */
> > > > +#define FSL_QDMA_CQIDR_SQT 0x8000
> > >
> > > BIT() ?
> >
> > Sorry, Maybe I should replace 0x8000 to BIT(15).
>
> yes please
>
> > > > +u64 pre_addr, pre_queue;
> > >
> > > why do we have a global?
> >
> > Let's us see qDMA that how is works?
> >
> > First, the status notification for DMA jobs are reported back to the status
> queue.
> > Status information is carried within the command descriptor
> > status/command field, bits 120-127. The command descriptor dequeue
> > pointer advances only after the transaction has completed and the status
> information field has been updated.
> >
> > Then, the command descriptor address field wiil pointer to the command
> > descriptor in its original format. It is the responsibity of the
> > address of the status queue consumer to deallocate buffers as needed when
> the command descriptor address pointer is non-zero.
> >
> > More details of the Status Queue can be found in QorIQ Layerscape Soc
> datasheet.
> >
> > So, these variable is used to record latest value that command
> > descriptor queue and status field.
> >
> > Every time variables value is zero when set these variable to local, that's not
> what I want.
>
> Why not store them in driver context?
>
okay, maybe I should remove these variable to private struct?
> > > > +static void fsl_qdma_comp_fill_memcpy(struct fsl_qdma_comp
> *fsl_comp,
> > > > + dma_addr_t dst, dma_addr_t src, u32 len) {
> > > > + struct fsl_qdma_format *ccdf, *csgf_desc, *csgf_src, *csgf_dest;
> > > > + struct fsl_qdma_sdf *sdf;
> > > > + struct fsl_qdma_ddf *ddf;
> > > > +
> > > > + ccdf = (struct fsl_qdma_format *)fsl_comp->virt_addr;
> > >
> > > Cast are not required to/away from void
> > >
> >
> > Does means: remove force conver?
>
> yes and it would work
>
> > > > +static struct fsl_qdma_comp *fsl_qdma_request_enqueue_desc(
> > > > + struct fsl_qdma_chan *fsl_chan,
> > > > + unsigned int dst_nents,
> > > > + unsigned int src_nents)
> > > > +{
> > > > + struct fsl_qdma_comp *comp_temp;
> > > > + struct fsl_qdma_sg *sg_block;
> > > > + struct fsl_qdma_queue *queue = fsl_chan->queue;
> > > > + unsigned long flags;
> > > > + unsigned int dst_sg_entry_block, src_sg_entry_block,
> > > > +sg_entry_total, i;
> > > > +
> > > > + spin_lock_irqsave(&queue->queue_lock, flags);
> > > > + if (list_empty(&queue->comp_free)) {
> > > > + spin_unlock_irqrestore(&queue->queue_lock, flags);
> > > > + comp_temp = kzalloc(sizeof(*comp_temp), GFP_KERNEL);
> > > > + if (!comp_temp)
> > > > + return NULL;
> > > > + comp_temp->virt_addr = dma_pool_alloc(queue->comp_pool,
> > > > + GFP_KERNEL,
> > > > + &comp_temp->bus_addr);
> > > > + if (!comp_temp->virt_addr) {
> > > > + kfree(comp_temp);
> > > > + return NULL;
> > > > + }
> > > > +
> > > > + } else {
> > > > + comp_temp = list_first_entry(&queue->comp_free,
> > > > + struct fsl_qdma_comp,
> > > > + list);
> > > > + list_del(&comp_temp->list);
> > > > + spin_unlock_irqrestore(&queue->queue_lock, flags);
> > > > + }
> > > > +
> > > > + if (dst_nents != 0)
> > > > + dst_sg_entry_block = dst_nents /
> > > > + (FSL_QDMA_EXPECT_SG_ENTRY_NUM - 1) + 1;
> > >
> > > DIV_ROUND_UP()?
> > >
> >
> > The DIV_ROUND_UP() definition see below:
> >
> > #define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP #define
> > __KERNEL_DIV_ROUND_UP(n ,d) (((n) + (d) - 1) / (d))
> >
> > But here is 'd / (n - 1) + 1' ?
>
> Yeah this doesn't look apt here, check if any other macros suits...
>
Got it.
> > > > + memset(sg_block->virt_addr, 0,
> > > > + FSL_QDMA_EXPECT_SG_ENTRY_NUM * 16);
> > >
> > > why FSL_QDMA_EXPECT_SG_ENTRY_NUM * 16? and not what you
> allocated?
> > >
> >
> > see line 497.
> > The sg_pool buffer size created is FSL_QDMA_EXPECT_SG_ENTRY_NUM *
> 16.
>
> Please document this
>
Add comment to this?
> > > > +static int fsl_qdma_queue_transfer_complete(struct
> > > > +fsl_qdma_engine
> > > > +*fsl_qdma) {
> > > > + struct fsl_qdma_queue *fsl_queue = fsl_qdma->queue;
> > > > + struct fsl_qdma_queue *fsl_status = fsl_qdma->status;
> > > > + struct fsl_qdma_queue *temp_queue;
> > > > + struct fsl_qdma_comp *fsl_comp;
> > > > + struct fsl_qdma_format *status_addr;
> > > > + struct fsl_qdma_format *csgf_src;
> > > > + void __iomem *block = fsl_qdma->block_base;
> > > > + u32 reg, i;
> > > > + bool duplicate, duplicate_handle;
> > > > +
> > > > + while (1) {
> > > > + duplicate = 0;
> > > > + duplicate_handle = 0;
> > > > + reg = qdma_readl(fsl_qdma, block + FSL_QDMA_BSQSR);
> > > > + if (reg & FSL_QDMA_BSQSR_QE)
> > > > + return 0;
> > > > + status_addr = fsl_status->virt_head;
> > > > + if (qdma_ccdf_get_queue(status_addr) == pre_queue &&
> > > > + qdma_ccdf_addr_get64(status_addr) == pre_addr)
> > > > + duplicate = 1;
> > > > + i = qdma_ccdf_get_queue(status_addr);
> > > > + pre_queue = qdma_ccdf_get_queue(status_addr);
> > > > + pre_addr = qdma_ccdf_addr_get64(status_addr);
> > > > + temp_queue = fsl_queue + i;
> > > > + spin_lock(&temp_queue->queue_lock);
> > > > + if (list_empty(&temp_queue->comp_used)) {
> > > > + if (duplicate)
> > > > + duplicate_handle = 1;
> > > > + else {
> > > > + spin_unlock(&temp_queue->queue_lock);
> > > > + return -1;
> > >
> > > -1? really. You are in while(1) wouldn't break make sense here?
> > >
> >
> > Does means: using break?
>
> it means two things, we don't use return -1, if it is valid error then return a
> proper kernel error code second, since it is a while loop, do you want to use a
> break?
>
Got it, Thanks.
> --
> ~Vinod
> --
> To unsubscribe from this list: send the line "unsubscribe dmaengine" in the
> body of a message to majordomo@vger.kernel.org More majordomo info at
> https://emea01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fvger.
> kernel.org%2Fmajordomo-info.html&data=02%7C01%7Cwen.he_1%40nxp.co
> m%7Cedcdbb818c534ec70b3108d5bc76d7b3%7C686ea1d3bc2b4c6fa92cd9
> 9c5c301635%7C0%7C0%7C636622140978593180&sdata=TuE40l7HejTz%2Bq
> S8LibIkdYV84Esh5sfD1jSIzvAwTY%3D&reserved=0
^ permalink raw reply
* Re: [PATCH v2 12/26] drm/sun4i: Add support for multiple DW HDMI PHY clock parents
From: Maxime Ripard @ 2018-05-18 10:01 UTC (permalink / raw)
To: Jagan Teki
Cc: Chen-Yu Tsai, Icenowy Zheng, Jernej Skrabec, Rob Herring,
Mark Rutland, Catalin Marinas, Will Deacon, David Airlie,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Michael Turquette,
Stephen Boyd, linux-clk-u79uwXL29TY76Z2rM5mHXA, Michael Trimarchi,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <20180518094536.17201-13-jagan-dyjBcgdgk7Pe9wHmmfpqLFaTQe2KTcn/@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 8540 bytes --]
On Fri, May 18, 2018 at 03:15:22PM +0530, Jagan Teki wrote:
> From: Jernej Skrabec <jernej.skrabec-gGgVlfcn5nU@public.gmane.org>
>
> Some SoCs with DW HDMI have multiple possible clock parents, like A64
> and R40.
>
> Expand HDMI PHY clock driver to support second clock parent.
>
> Signed-off-by: Jernej Skrabec <jernej.skrabec-gGgVlfcn5nU@public.gmane.org>
> Signed-off-by: Jagan Teki <jagan-dyjBcgdgk7Pe9wHmmfpqLFaTQe2KTcn/@public.gmane.org>
> ---
> Changes for v2:
> - new patch
>
> drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h | 9 ++-
> drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 33 ++++++++---
> drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c | 89 ++++++++++++++++++++++--------
> 3 files changed, 96 insertions(+), 35 deletions(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> index 79154f0f674a..303189d6602c 100644
> --- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> @@ -98,7 +98,8 @@
> #define SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN BIT(29)
> #define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN BIT(28)
> #define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 BIT(27)
> -#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL BIT(26)
> +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK BIT(26)
> +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT 26
> #define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN BIT(25)
> #define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x) ((x) << 22)
> #define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x) ((x) << 20)
> @@ -146,7 +147,7 @@
> struct sun8i_hdmi_phy;
>
> struct sun8i_hdmi_phy_variant {
> - bool has_phy_clk;
> + int phy_clk_num;
> void (*phy_init)(struct sun8i_hdmi_phy *phy);
> void (*phy_disable)(struct dw_hdmi *hdmi,
> struct sun8i_hdmi_phy *phy);
> @@ -160,6 +161,7 @@ struct sun8i_hdmi_phy {
> struct clk *clk_mod;
> struct clk *clk_phy;
> struct clk *clk_pll0;
> + struct clk *clk_pll1;
> unsigned int rcal;
> struct regmap *regs;
> struct reset_control *rst_phy;
> @@ -188,6 +190,7 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi);
> void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
> const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void);
>
> -int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev);
> +int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
> + int clk_num);
>
> #endif /* _SUN8I_DW_HDMI_H_ */
> diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> index 5a52fc489a9d..0eadf087fc46 100644
> --- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> @@ -183,7 +183,13 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
> regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
> SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0);
>
> - regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, pll_cfg1_init);
> + /*
> + * NOTE: We have to be careful not to overwrite PHY parent
> + * clock selection bit and clock divider.
> + */
> + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
> + (u32)~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
> + pll_cfg1_init);
> regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
> (u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
> pll_cfg2_init);
> @@ -232,7 +238,7 @@ static int sun8i_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
> regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG,
> SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, val);
>
> - if (phy->variant->has_phy_clk)
> + if (phy->variant->phy_clk_num)
> clk_set_rate(phy->clk_phy, mode->crtc_clock * 1000);
>
> return phy->variant->phy_config(hdmi, phy, mode->crtc_clock * 1000);
> @@ -393,7 +399,7 @@ static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = {
> };
>
> static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = {
> - .has_phy_clk = true,
> + .phy_clk_num = 1,
> .phy_init = &sun8i_hdmi_phy_init_h3,
> .phy_disable = &sun8i_hdmi_phy_disable_h3,
> .phy_config = &sun8i_hdmi_phy_config_h3,
> @@ -464,7 +470,7 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
> goto err_put_clk_bus;
> }
>
> - if (phy->variant->has_phy_clk) {
> + if (phy->variant->phy_clk_num) {
> phy->clk_pll0 = of_clk_get_by_name(node, "pll-0");
> if (IS_ERR(phy->clk_pll0)) {
> dev_err(dev, "Could not get pll-0 clock\n");
> @@ -472,7 +478,16 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
> goto err_put_clk_mod;
> }
>
> - ret = sun8i_phy_clk_create(phy, dev);
> + if (phy->variant->phy_clk_num) {
> + phy->clk_pll1 = of_clk_get_by_name(node, "pll-1");
> + if (IS_ERR(phy->clk_pll1)) {
> + dev_err(dev, "Could not get pll-1 clock\n");
> + ret = PTR_ERR(phy->clk_pll1);
> + goto err_put_clk_mod;
> + }
> + }
> +
You have a bug here. If phy_clk_num == 1, you'll still try to lookup
pll-1.
And this is a bit sloppy, since if phy_clk_num == 3, you won't try to
lookup pll-2 either.
> + ret = sun8i_phy_clk_create(phy, dev, phy->variant->phy_clk_num);
> if (ret) {
> dev_err(dev, "Couldn't create the PHY clock\n");
> goto err_put_clk_pll0;
> @@ -515,8 +530,8 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
> err_put_rst_phy:
> reset_control_put(phy->rst_phy);
> err_put_clk_pll0:
> - if (phy->variant->has_phy_clk)
> - clk_put(phy->clk_pll0);
> + clk_put(phy->clk_pll0);
> + clk_put(phy->clk_pll1);
> err_put_clk_mod:
> clk_put(phy->clk_mod);
> err_put_clk_bus:
> @@ -536,8 +551,8 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi)
>
> reset_control_put(phy->rst_phy);
>
> - if (phy->variant->has_phy_clk)
> - clk_put(phy->clk_pll0);
> + clk_put(phy->clk_pll0);
> + clk_put(phy->clk_pll1);
> clk_put(phy->clk_mod);
> clk_put(phy->clk_bus);
> }
> diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
> index faea449812f8..85b12fc96dbc 100644
> --- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
> +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
> @@ -22,29 +22,36 @@ static int sun8i_phy_clk_determine_rate(struct clk_hw *hw,
> {
> unsigned long rate = req->rate;
> unsigned long best_rate = 0;
> - struct clk_hw *parent;
> + struct clk_hw *best_parent = NULL;
> + struct clk_hw *parent = NULL;
> int best_div = 1;
> - int i;
> + int i, p;
>
> - parent = clk_hw_get_parent(hw);
> -
> - for (i = 1; i <= 16; i++) {
> - unsigned long ideal = rate * i;
> - unsigned long rounded;
> -
> - rounded = clk_hw_round_rate(parent, ideal);
> -
> - if (rounded == ideal) {
> - best_rate = rounded;
> - best_div = i;
> - break;
> - }
> + for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
> + parent = clk_hw_get_parent_by_index(hw, p);
> + if (!parent)
> + continue;
>
> - if (!best_rate ||
> - abs(rate - rounded / i) <
> - abs(rate - best_rate / best_div)) {
> - best_rate = rounded;
> - best_div = i;
> + for (i = 1; i <= 16; i++) {
> + unsigned long ideal = rate * i;
> + unsigned long rounded;
> +
> + rounded = clk_hw_round_rate(parent, ideal);
> +
> + if (rounded == ideal) {
> + best_rate = rounded;
> + best_div = i;
> + best_parent = parent;
> + break;
> + }
> +
> + if (!best_rate ||
> + abs(rate - rounded / i) <
> + abs(rate - best_rate / best_div)) {
> + best_rate = rounded;
> + best_div = i;
> + best_parent = parent;
> + }
> }
> }
>
> @@ -95,22 +102,58 @@ static int sun8i_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> return 0;
> }
>
> +static u8 sun8i_phy_clk_get_parent(struct clk_hw *hw)
> +{
> + struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
> + u32 reg;
> +
> + regmap_read(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, ®);
> + reg = (reg & SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK) >>
> + SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT;
> +
> + return reg;
> +}
> +
> +static int sun8i_phy_clk_set_parent(struct clk_hw *hw, u8 index)
> +{
> + struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
> +
> + if (index > 1)
> + return -EINVAL;
> +
> + regmap_update_bits(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
> + SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
> + index << SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT);
> +
> + return 0;
> +}
> +
The DT bindings changes and the clk changes should be part of separate
patches.
Maxime
--
Maxime Ripard, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox