RE: [PATCH v2 2/2] firmware: add exynos acpm driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Tudor

> -----Original Message-----
> From: Tudor Ambarus <tudor.ambarus@xxxxxxxxxx>
> Sent: Thursday, October 17, 2024 10:07 PM
> To: jassisinghbrar@xxxxxxxxx; krzk@xxxxxxxxxx
> Cc: alim.akhtar@xxxxxxxxxxx; mst@xxxxxxxxxx; javierm@xxxxxxxxxx;
> tzimmermann@xxxxxxx; bartosz.golaszewski@xxxxxxxxxx;
> luzmaximilian@xxxxxxxxx; sudeep.holla@xxxxxxx;
> conor.dooley@xxxxxxxxxxxxx; bjorn@xxxxxxxxxxxx; ulf.hansson@xxxxxxxxxx;
> linux-samsung-soc@xxxxxxxxxxxxxxx; linux-kernel@xxxxxxxxxxxxxxx; linux-
> arm-kernel@xxxxxxxxxxxxxxxxxxx; marcan@xxxxxxxxx; neal@xxxxxxxxx;
> alyssa@xxxxxxxxxxxxx; broonie@xxxxxxxxxx; andre.draszik@xxxxxxxxxx;
> willmcvicker@xxxxxxxxxx; peter.griffin@xxxxxxxxxx; kernel-
> team@xxxxxxxxxxx; vincent.guittot@xxxxxxxxxx; daniel.lezcano@xxxxxxxxxx;
> Tudor Ambarus <tudor.ambarus@xxxxxxxxxx>
> Subject: [PATCH v2 2/2] firmware: add exynos acpm driver
> 
> ACPM (Alive Clock and Power Manager) is a firmware that operates on the
> APM (Active Power Management) module that handles overall power
> management activities. ACPM and masters regard each other as
> independent hardware component and communicate with each other using
> mailbox messages and shared memory.
> 
> The mailbox channels are initialized based on the configuration data found
at
> a specific offset into the shared memory (mmio-sram). The configuration
> data consists of channel id, message and queue lengths, pointers to the RX
> and TX queues which are also part of the SRAM, and whether RX works by
> polling or interrupts. All known clients of this driver are using polling
channels,
> thus the driver implements for now just polling mode.
> 
> Add support for the exynos acpm core driver. Helper drivers will follow.
> These will construct the mailbox messages in the format expected by the
> firmware.
> 
> Signed-off-by: Tudor Ambarus <tudor.ambarus@xxxxxxxxxx>
> ---
>  drivers/firmware/Kconfig                    |   1 +
>  drivers/firmware/Makefile                   |   1 +
>  drivers/firmware/samsung/Kconfig            |  11 +
>  drivers/firmware/samsung/Makefile           |   3 +
>  drivers/firmware/samsung/exynos-acpm.c      | 703
> ++++++++++++++++++++
>  include/linux/mailbox/exynos-acpm-message.h |  21 +
>  6 files changed, 740 insertions(+)
>  create mode 100644 drivers/firmware/samsung/Kconfig  create mode
> 100644 drivers/firmware/samsung/Makefile  create mode 100644
> drivers/firmware/samsung/exynos-acpm.c
>  create mode 100644 include/linux/mailbox/exynos-acpm-message.h
> 
> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index
> 71d8b26c4103..24edb956831b 100644
> --- a/drivers/firmware/Kconfig
> +++ b/drivers/firmware/Kconfig
> @@ -267,6 +267,7 @@ source "drivers/firmware/meson/Kconfig"
>  source "drivers/firmware/microchip/Kconfig"
>  source "drivers/firmware/psci/Kconfig"
>  source "drivers/firmware/qcom/Kconfig"
> +source "drivers/firmware/samsung/Kconfig"
>  source "drivers/firmware/smccc/Kconfig"
>  source "drivers/firmware/tegra/Kconfig"
>  source "drivers/firmware/xilinx/Kconfig"
> diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index
> 7a8d486e718f..91efcc868a05 100644
> --- a/drivers/firmware/Makefile
> +++ b/drivers/firmware/Makefile
> @@ -33,6 +33,7 @@ obj-y				+= efi/
>  obj-y				+= imx/
>  obj-y				+= psci/
>  obj-y				+= qcom/
> +obj-y				+= samsung/
>  obj-y				+= smccc/
>  obj-y				+= tegra/
>  obj-y				+= xilinx/
> diff --git a/drivers/firmware/samsung/Kconfig
> b/drivers/firmware/samsung/Kconfig
> new file mode 100644
> index 000000000000..f908773c1441
> --- /dev/null
> +++ b/drivers/firmware/samsung/Kconfig
> @@ -0,0 +1,11 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config EXYNOS_ACPM

This looks misleading to me, as you mentioned below, ACPM is a FW which runs
on APM module, and 
The proposed driver is a communication method between Application processor
and APM module,
Which is via MAILBOX.
So preferably EXYNOS_MAILBOX_APM is more meaningful here.

> +	tristate "Exynos ACPM (Alive Clock and Power Manager) driver
> support"
> +	select MAILBOX
> +	help
> +	  ACPM is a firmware that operates on the APM (Active Power
> Management)
> +	  module that handles overall power management activities. ACPM
> and
> +	  masters regard each other as independent hardware component
> and
> +	  communicate with each other using mailbox messages and shared
> memory.
> +	  This module provides the means to communicate with the ACPM
> firmware.
> diff --git a/drivers/firmware/samsung/Makefile
> b/drivers/firmware/samsung/Makefile
> new file mode 100644
> index 000000000000..35ff3076bbea
> --- /dev/null
> +++ b/drivers/firmware/samsung/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-$(CONFIG_EXYNOS_ACPM)	+= exynos-acpm.o
> diff --git a/drivers/firmware/samsung/exynos-acpm.c
> b/drivers/firmware/samsung/exynos-acpm.c
> new file mode 100644
> index 000000000000..c3ad4dc7a9e0
> --- /dev/null
> +++ b/drivers/firmware/samsung/exynos-acpm.c
> @@ -0,0 +1,703 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright 2020 Samsung Electronics Co., Ltd.
> + * Copyright 2020 Google LLC.
> + * Copyright 2024 Linaro Ltd.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitmap.h>
> +#include <linux/bits.h>
> +#include <linux/container_of.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/mailbox_controller.h>
> +#include <linux/mailbox/exynos-acpm-message.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#define EXYNOS_ACPM_MCUCTRL		0x0	/* Mailbox Control
> Register */
> +#define EXYNOS_ACPM_INTCR0		0x24	/* Interrupt Clear
> Register 0 */
> +#define EXYNOS_ACPM_INTMR0		0x28	/* Interrupt Mask
> Register 0 */
> +#define EXYNOS_ACPM_INTSR0		0x2c	/* Interrupt Status
> Register 0 */
> +#define EXYNOS_ACPM_INTMSR0		0x30	/* Interrupt Mask
> Status Register 0 */
> +#define EXYNOS_ACPM_INTGR1		0x40	/* Interrupt
> Generation Register 1 */
> +#define EXYNOS_ACPM_INTMR1		0x48	/* Interrupt Mask
> Register 1 */
> +#define EXYNOS_ACPM_INTSR1		0x4c	/* Interrupt Status
> Register 1 */
> +#define EXYNOS_ACPM_INTMSR1		0x50	/* Interrupt Mask
> Status Register 1 */
> +
> +#define EXYNOS_ACPM_INTMR0_MASK		GENMASK(15, 0)
> +#define EXYNOS_ACPM_PROTOCOL_SEQNUM	GENMASK(21, 16)
> +
> +/* The unit of counter is 20 us. 5000 * 20 = 100 ms */
> +#define EXYNOS_ACPM_POLL_TIMEOUT	5000
> +#define EXYNOS_ACPM_TX_TIMEOUT_US	500000
> +
> +/**
> + * struct exynos_acpm_shmem - mailbox shared memory configuration
> information.
> + * @reserved:	reserved for future use.
> + * @chans:	offset to array of struct exynos_acpm_shmem_chan.
> + * @reserved1:	reserved for future use.
> + * @num_chans:	number of channels.
> + */
> +struct exynos_acpm_shmem {
> +	u32 reserved[2];
> +	u32 chans;
> +	u32 reserved1[3];
> +	u32 num_chans;
> +};
> +
> +/**
> + * struct exynos_acpm_shmem_chan - descriptor of a shared memory
> channel.
> + *
> + * @id:			channel ID.
> + * @reserved:		reserved for future use.
> + * @rx_rear:		rear pointer of RX queue.
> + * @rx_front:		front pointer of RX queue.
> + * @rx_base:		base address of RX queue.
> + * @reserved1:		reserved for future use.
> + * @tx_rear:		rear pointer of TX queue.
> + * @tx_front:		front pointer of TX queue.
> + * @tx_base:		base address of TX queue.
> + * @qlen:		queue length. Applies to both TX/RX queues.
> + * @mlen:		message length. Applies to both TX/RX queues.
> + * @reserved2:		reserved for future use.
> + * @polling:		true when the channel works on polling.
> + */
> +struct exynos_acpm_shmem_chan {
> +	u32 id;
> +	u32 reserved[3];
> +	u32 rx_rear;
> +	u32 rx_front;
> +	u32 rx_base;
> +	u32 reserved1[3];
> +	u32 tx_rear;
> +	u32 tx_front;
> +	u32 tx_base;
> +	u32 qlen;
> +	u32 mlen;
> +	u32 reserved2[2];
> +	u32 polling;
> +};
> +
> +/**
> + * struct exynos_acpm_queue - exynos acpm queue.
> + *
> + * @rear:	rear address of the queue.
> + * @front:	front address of the queue.
> + * @base:	base address of the queue.
> + */
> +struct exynos_acpm_queue {
> +	void __iomem *rear;
> +	void __iomem *front;
> +	void __iomem *base;
> +};
> +
> +/**
> + * struct exynos_acpm_rx_data - RX queue data.
> + *
> + * @cmd:	pointer to where the data shall be saved.
> + * @response:	true if the client expects the RX data.
> + */
> +struct exynos_acpm_rx_data {
> +	u32 *cmd;
> +	bool response;
> +};
> +
> +#define EXYNOS_ACPM_SEQNUM_MAX    64
> +
> +/**
> + * struct exynos_acpm_chan - driver internal representation of a channel.
> + * @tx:		TX queue. The enqueue is done by the host.
> + *			- front index is written by the host.
> + *			- rear index is written by the firmware.
> + *
> + * @rx:		RX queue. The enqueue is done by the firmware.
> + *			- front index is written by the firmware.
> + *			- rear index is written by the host.
> + * @rx_lock:	protects RX queue. The RX queue is accessed just in
> + *		process context.
> + * @tx_lock:	protects TX queue.
> + * @qlen:	queue length. Applies to both TX/RX queues.
> + * @mlen:	message length. Applies to both TX/RX queues.
> + * @seqnum:	sequence number of the last message enqueued on TX
> queue.
> + * @id:		channel ID.
> + * @polling:	true when the channel works on polling.
> + * @bitmap_seqnum: bitmap that tracks the messages on the TX/RX
> queues.
> + * @rx_data:	internal buffer used to drain the RX queue.
> + */
> +struct exynos_acpm_chan {
> +	struct exynos_acpm_queue tx;
> +	struct exynos_acpm_queue rx;
> +	struct mutex rx_lock;
> +	spinlock_t tx_lock;
> +
> +	unsigned int qlen;
> +	unsigned int mlen;
> +	u8 seqnum;
> +	u8 id;
> +	bool polling;
> +
> +	DECLARE_BITMAP(bitmap_seqnum, EXYNOS_ACPM_SEQNUM_MAX
> - 1);
> +	struct exynos_acpm_rx_data
> rx_data[EXYNOS_ACPM_SEQNUM_MAX]; };
> +
> +/**
> + * struct exynos_acpm - driver's private data.
> + * @shmem:	pointer to the SRAM configuration data.
> + * @chans:	pointer to the ACPM channel parameters retrieved from
> SRAM.
> + * @sram_base:	base address of SRAM.
> + * @regs:	mailbox registers base address.
> + * @mbox:	pointer to the mailbox controller.
> + * @wq:		pointer to workqueue.
> + * @dev:	pointer to the exynos-acpm device.
> + * @pclk:	pointer to the mailbox peripheral clock.
> + * @num_chans:	number of channels available for this controller.
> + */
> +struct exynos_acpm {
> +	struct exynos_acpm_shmem *shmem;
> +	struct exynos_acpm_chan *chans;
> +	void __iomem *sram_base;
> +	void __iomem *regs;
> +	struct mbox_controller *mbox;
> +	struct workqueue_struct *wq;
> +	struct device *dev;
> +	struct clk *pclk;
> +	u32 num_chans;
> +};
> +
> +/**
> + * struct exynos_acpm_work_data - data structure representing the work.
> + * @mbox_chan:	pointer to the mailbox channel.
> + * @req:	pointer to the mailbox request.
> + * @callback:	pointer to a callback function to be invoked upon
> + *		completion of this request.
> + * @work:	describes the task to be executed.
> + */
> +struct exynos_acpm_work_data {
> +	struct mbox_chan *mbox_chan;
> +	struct mbox_request *req;
> +	void (*callback)(struct exynos_acpm_work_data *work_data, int
> status);
> +	struct work_struct work;
> +};
> +
> +static int exynos_acpm_get_rx(struct mbox_chan *mbox_chan,
> +			      struct mbox_request *req)
> +{
> +	struct exynos_acpm_chan *chan = mbox_chan->con_priv;
> +	struct exynos_acpm_message *tx = req->tx;
> +	struct exynos_acpm_message *rx = req->rx;
> +	struct exynos_acpm_rx_data *rx_data;
> +	const void __iomem *base, *addr;
> +	u32 rx_front, rx_seqnum, tx_seqnum, seqnum;
> +	u32 i, val, mlen;
> +	bool rx_set = false;
> +
> +	rx_front = readl_relaxed(chan->rx.front);
> +	i = readl_relaxed(chan->rx.rear);
> +
> +	/* Bail out if RX is empty. */
> +	if (i == rx_front)
> +		return 0;
> +
> +	base = chan->rx.base;
> +	mlen = chan->mlen;
> +
> +	tx_seqnum = FIELD_GET(EXYNOS_ACPM_PROTOCOL_SEQNUM, tx-
> >cmd[0]);
> +
> +	/* Drain RX queue. */
> +	do {
> +		/* Read RX seqnum. */
> +		addr = base + mlen * i;
> +		val = readl_relaxed(addr);
> +
> +		rx_seqnum =
> FIELD_GET(EXYNOS_ACPM_PROTOCOL_SEQNUM, val);
> +		if (!rx_seqnum)
> +			return -EIO;
> +		/*
> +		 * mssg seqnum starts with value 1, whereas the driver
> considers
> +		 * the first mssg at index 0.
> +		 */
> +		seqnum = rx_seqnum - 1;
> +		rx_data = &chan->rx_data[seqnum];
> +
> +		if (rx_data->response) {
> +			if (rx_seqnum == tx_seqnum) {
> +				__ioread32_copy(rx->cmd, addr, req->rxlen /
> 4);
> +				rx_set = true;
> +				clear_bit(seqnum, chan->bitmap_seqnum);
> +			} else {
> +				/*
> +				 * The RX data corresponds to another
> request.
> +				 * Save the data to drain the queue, but
don't
> +				 * clear yet the bitmap. It will be cleared
> +				 * after the response is copied to the
request.
> +				 */
> +				__ioread32_copy(rx_data->cmd, addr,
> +						req->rxlen / 4);
> +			}
> +		} else {
> +			clear_bit(seqnum, chan->bitmap_seqnum);
> +		}
> +
> +		i = (i + 1) % chan->qlen;
> +	} while (i != rx_front);
> +
> +	/* We saved all responses, mark RX empty. */
> +	writel_relaxed(rx_front, chan->rx.rear);
> +
> +	/* Flush SRAM posted writes. */
> +	readl_relaxed(chan->rx.front);
> +
> +	/*
> +	 * If the response was not in this iteration of the queue, check if
the
> +	 * RX data was previously saved.
> +	 */
> +	rx_data = &chan->rx_data[tx_seqnum - 1];
> +	if (!rx_set && rx_data->response) {
> +		rx_seqnum =
> FIELD_GET(EXYNOS_ACPM_PROTOCOL_SEQNUM,
> +				      rx_data->cmd[0]);
> +
> +		if (rx_seqnum == tx_seqnum) {
> +			memcpy(rx->cmd, rx_data->cmd, req->rxlen);
> +			clear_bit(rx_seqnum - 1, chan->bitmap_seqnum);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int exynos_acpm_dequeue_by_polling(struct mbox_chan
> *mbox_chan,
> +					  struct mbox_request *req)
> +{
> +	struct exynos_acpm_chan *chan = mbox_chan->con_priv;
> +	struct exynos_acpm_message *tx = req->tx;
> +	struct device *dev = mbox_chan->mbox->dev;
> +	unsigned int cnt_20us = 0;
> +	u32 seqnum;
> +	int ret;
> +
> +	seqnum = FIELD_GET(EXYNOS_ACPM_PROTOCOL_SEQNUM, tx-
> >cmd[0]);
> +
> +	do {
> +		ret = mutex_lock_interruptible(&chan->rx_lock);
> +		if (ret)
> +			return ret;
> +		ret = exynos_acpm_get_rx(mbox_chan, req);
> +		mutex_unlock(&chan->rx_lock);
> +		if (ret)
> +			return ret;
> +
> +		if (!test_bit(seqnum - 1, chan->bitmap_seqnum)) {
> +			dev_vdbg(dev, "cnt_20us = %d.\n", cnt_20us);
> +			return 0;
> +		}
> +
> +		/* Determined experimentally. */
> +		usleep_range(20, 30);
> +		cnt_20us++;
> +	} while (cnt_20us < EXYNOS_ACPM_POLL_TIMEOUT);
> +
> +	dev_err(dev, "Timeout! ch:%u s:%u bitmap:%lx, cnt_20us = %d.\n",
> +		chan->id, seqnum, chan->bitmap_seqnum[0], cnt_20us);
> +
> +	return -ETIME;
> +}
> +
> +static void exynos_acpm_done(struct exynos_acpm_work_data
> *work_data,
> +int status) {
> +	struct mbox_request *req = work_data->req;
> +
> +	kfree(work_data);
> +	mbox_request_complete(req, status);
> +}
> +
> +static void exynos_acpm_work_handler(struct work_struct *work) {
> +	struct exynos_acpm_work_data *work_data =
> +		container_of(work, struct exynos_acpm_work_data, work);
> +	struct mbox_chan *mbox_chan = work_data->mbox_chan;
> +	int ret;
> +
> +	ret = exynos_acpm_dequeue_by_polling(mbox_chan, work_data-
> >req);
> +	work_data->callback(work_data, ret);
> +}
> +
> +static struct exynos_acpm_work_data *
> +	exynos_acpm_init_work(struct mbox_chan *mbox_chan,
> +			      struct mbox_request *req)
> +{
> +	struct exynos_acpm_work_data *work_data;
> +	gfp_t gfp = (req->flags & MBOX_REQ_MAY_SLEEP) ? GFP_KERNEL :
> +GFP_ATOMIC;
> +
> +	work_data = kmalloc(sizeof(*work_data), gfp);
> +	if (!work_data)
> +		return ERR_PTR(-ENOMEM);
> +
> +	work_data->mbox_chan = mbox_chan;
> +	work_data->req = req;
> +	work_data->callback = exynos_acpm_done;
> +	INIT_WORK(&work_data->work, exynos_acpm_work_handler);
> +
> +	return work_data;
> +}
> +
> +static void exynos_acpm_prepare_request(struct mbox_chan *mbox_chan,
> +					struct mbox_request *req)
> +{
> +	struct exynos_acpm_chan *chan = mbox_chan->con_priv;
> +	struct exynos_acpm_message *tx = req->tx;
> +	struct exynos_acpm_rx_data *rx_data;
> +
> +	/* Prevent chan->seqnum from being re-used */
> +	do {
> +		if (++chan->seqnum == EXYNOS_ACPM_SEQNUM_MAX)
> +			chan->seqnum = 1;
> +	} while (test_bit(chan->seqnum - 1, chan->bitmap_seqnum));
> +
> +	tx->cmd[0] |= FIELD_PREP(EXYNOS_ACPM_PROTOCOL_SEQNUM,
> chan->seqnum);
> +
> +	/* Clear data for upcoming responses */
> +	rx_data = &chan->rx_data[chan->seqnum - 1];
> +	memset(rx_data->cmd, 0, sizeof(*(rx_data->cmd)) * chan->mlen);
> +	if (req->rx)
> +		rx_data->response = true;
> +
> +	/* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */
> +	set_bit(chan->seqnum - 1, chan->bitmap_seqnum); }
> +
> +static int exynos_acpm_wait_for_queue_slots(struct mbox_chan
> *mbox_chan,
> +					    u32 next_tx_front)
> +{
> +	struct exynos_acpm_chan *chan = mbox_chan->con_priv;
> +	struct device *dev = mbox_chan->mbox->dev;
> +	u32 val, ret;
> +
> +	/*
> +	 * Wait for RX front to keep up with TX front. Make sure there's at
> +	 * least one element between them.
> +	 */
> +	ret = readl_relaxed_poll_timeout_atomic(chan->rx.front, val,
> +						next_tx_front != val, 1,
> +
> 	EXYNOS_ACPM_TX_TIMEOUT_US);
> +	if (ret) {
> +		dev_err(dev, "RX front can not keep up with TX front.\n");
> +		return ret;
> +	}
> +
> +	ret = readl_relaxed_poll_timeout_atomic(chan->tx.rear, val,
> +						next_tx_front != val, 1,
> +
> 	EXYNOS_ACPM_TX_TIMEOUT_US);
> +	if (ret)
> +		dev_err(dev, "TX queue is full.\n");
> +
> +	return ret;
> +}
> +
> +static int exynos_acpm_send_request(struct mbox_chan *mbox_chan,
> +				    struct mbox_request *req)
> +{
> +	struct exynos_acpm *exynos_acpm = dev_get_drvdata(mbox_chan-
> >mbox->dev);
> +	struct exynos_acpm_chan *chan = mbox_chan->con_priv;
> +	struct exynos_acpm_message *tx = req->tx;
> +	struct exynos_acpm_work_data *work_data;
> +	u32 idx, tx_front;
> +	unsigned long flags;
> +	int ret;
> +
> +	if (!tx || !tx->cmd || req->txlen > chan->mlen ||
> +	    req->rxlen > chan->mlen)
> +		return -EINVAL;
> +
> +	work_data = exynos_acpm_init_work(mbox_chan, req);
> +	if (IS_ERR(work_data))
> +		return PTR_ERR(work_data);
> +
> +	spin_lock_irqsave(&chan->tx_lock, flags);
> +
> +	tx_front = readl_relaxed(chan->tx.front);
> +	idx = (tx_front + 1) % chan->qlen;
> +
> +	ret = exynos_acpm_wait_for_queue_slots(mbox_chan, idx);
> +	if (ret)
> +		goto exit;
> +
> +	exynos_acpm_prepare_request(mbox_chan, req);
> +
> +	/* Write TX command. */
> +	__iowrite32_copy(chan->tx.base + chan->mlen * tx_front, tx->cmd,
> +			 req->txlen / 4);
> +
> +	/* Advance TX front. */
> +	writel_relaxed(idx, chan->tx.front);
> +
> +	/* Flush SRAM posted writes. */
> +	readl_relaxed(chan->tx.front);
> +
> +	/* Generate ACPM interrupt. */
> +	writel_relaxed(BIT(chan->id), exynos_acpm->regs +
> EXYNOS_ACPM_INTGR1);
> +
> +	/* Flush mailbox controller posted writes. */
> +	readl_relaxed(exynos_acpm->regs + EXYNOS_ACPM_MCUCTRL);
> +
> +	spin_unlock_irqrestore(&chan->tx_lock, flags);
> +
> +	queue_work(exynos_acpm->wq, &work_data->work);
> +
> +	return -EINPROGRESS;
> +exit:
> +	spin_unlock_irqrestore(&chan->tx_lock, flags);
> +	kfree(work_data);
> +	return ret;
> +}
> +
> +static int exynos_acpm_chan_startup(struct mbox_chan *mbox_chan) {
> +	struct exynos_acpm_chan *chan = mbox_chan->con_priv;
> +
> +	if (!chan->polling) {
> +		dev_err(mbox_chan->mbox->dev, "IRQs not
> supported.\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct mbox_chan_ops exynos_acpm_chan_ops = {
> +	.send_request = exynos_acpm_send_request,
> +	.startup = exynos_acpm_chan_startup,
> +};
> +
> +static void __iomem *exynos_acpm_get_iomem_addr(void __iomem
> *base,
> +						void __iomem *addr)
> +{
> +	u32 offset;
> +
> +	offset = readl_relaxed(addr);
> +	return base + offset;
> +}
> +
> +static void exynos_acpm_rx_queue_init(struct exynos_acpm
> *exynos_acpm,
> +				      struct exynos_acpm_shmem_chan
> *shmem_chan,
> +				      struct exynos_acpm_queue *rx) {
> +	void __iomem *base = exynos_acpm->sram_base;
> +
> +	rx->base = exynos_acpm_get_iomem_addr(base, &shmem_chan-
> >tx_base);
> +	rx->front = exynos_acpm_get_iomem_addr(base, &shmem_chan-
> >tx_front);
> +	rx->rear = exynos_acpm_get_iomem_addr(base, &shmem_chan-
> >tx_rear); }
> +
> +static void exynos_acpm_tx_queue_init(struct exynos_acpm
> *exynos_acpm,
> +				      struct exynos_acpm_shmem_chan
> *shmem_chan,
> +				      struct exynos_acpm_queue *tx) {
> +	void __iomem *base = exynos_acpm->sram_base;
> +
> +	tx->base = exynos_acpm_get_iomem_addr(base, &shmem_chan-
> >rx_base);
> +	tx->front = exynos_acpm_get_iomem_addr(base, &shmem_chan-
> >rx_front);
> +	tx->rear = exynos_acpm_get_iomem_addr(base, &shmem_chan-
> >rx_rear); }
> +
> +static int exynos_acpm_alloc_cmds(struct exynos_acpm *exynos_acpm,
> +				  struct exynos_acpm_chan *chan)
> +{
> +	struct device *dev = exynos_acpm->dev;
> +	struct exynos_acpm_rx_data *rx_data;
> +	unsigned int mlen = chan->mlen;
> +	int i;
> +
> +	for (i = 0; i < EXYNOS_ACPM_SEQNUM_MAX; i++) {
> +		rx_data = &chan->rx_data[i];
> +		rx_data->cmd = devm_kcalloc(dev, mlen, sizeof(*(rx_data-
> >cmd)),
> +					    GFP_KERNEL);
> +		if (!rx_data->cmd)
> +			return -ENOMEM;
> +	}
> +
> +	return 0;
> +}
> +
> +static int exynos_acpm_chans_init(struct exynos_acpm *exynos_acpm) {
> +	struct exynos_acpm_shmem_chan *shmem_chans, *shmem_chan;
> +	struct exynos_acpm_shmem *shmem = exynos_acpm->shmem;
> +	struct mbox_chan *mbox_chan, *mbox_chans;
> +	struct exynos_acpm_chan *chan, *chans;
> +	struct device *dev = exynos_acpm->dev;
> +	int i, ret;
> +
> +	exynos_acpm->num_chans = readl_relaxed(&shmem->num_chans);
> +
> +	mbox_chans = devm_kcalloc(dev, exynos_acpm->num_chans,
> +				  sizeof(*mbox_chans), GFP_KERNEL);
> +	if (!mbox_chans)
> +		return -ENOMEM;
> +
> +	chans = devm_kcalloc(dev, exynos_acpm->num_chans,
> sizeof(*chans),
> +			     GFP_KERNEL);
> +	if (!chans)
> +		return -ENOMEM;
> +
> +	shmem_chans = exynos_acpm_get_iomem_addr(exynos_acpm-
> >sram_base,
> +						 &shmem->chans);
> +
> +	for (i = 0; i < exynos_acpm->num_chans; i++) {
> +		shmem_chan = &shmem_chans[i];
> +		mbox_chan = &mbox_chans[i];
> +		chan = &chans[i];
> +
> +		/* Set parameters for the mailbox channel. */
> +		mbox_chan->con_priv = chan;
> +		mbox_chan->mbox = exynos_acpm->mbox;
> +
> +		/* Set parameters for the ACPM channel. */
> +		chan->mlen = readl_relaxed(&shmem_chan->mlen);
> +
> +		ret = exynos_acpm_alloc_cmds(exynos_acpm, chan);
> +		if (ret)
> +			return ret;
> +
> +		chan->polling = readl_relaxed(&shmem_chan->polling);
> +		chan->id = readl_relaxed(&shmem_chan->id);
> +		chan->qlen = readl_relaxed(&shmem_chan->qlen);
> +
> +		exynos_acpm_rx_queue_init(exynos_acpm, shmem_chan,
> &chan->rx);
> +		exynos_acpm_tx_queue_init(exynos_acpm, shmem_chan,
> &chan->tx);
> +
> +		mutex_init(&chan->rx_lock);
> +		spin_lock_init(&chan->tx_lock);
> +
> +		dev_vdbg(dev, "ID = %d poll = %d, mlen = %d, qlen = %d\n",
> +			 chan->id, chan->polling, chan->mlen, chan->qlen);
> +	}
> +
> +	/* Save pointers to the ACPM and mailbox channels. */
> +	exynos_acpm->mbox->chans = mbox_chans;
> +	exynos_acpm->chans = chans;
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id exynos_acpm_match[] = {
> +	{ .compatible = "google,gs101-acpm" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, exynos_acpm_match);
> +
> +static int exynos_acpm_probe(struct platform_device *pdev) {
> +	struct device_node *node = pdev->dev.of_node;
> +	struct device *dev = &pdev->dev;
> +	struct exynos_acpm *exynos_acpm;
> +	struct mbox_controller *mbox;
> +	struct device_node *shmem;
> +	resource_size_t size;
> +	struct resource res;
> +	const __be32 *prop;
> +	int ret;
> +
> +	exynos_acpm = devm_kzalloc(dev, sizeof(*exynos_acpm),
> GFP_KERNEL);
> +	if (!exynos_acpm)
> +		return -ENOMEM;
> +
> +	mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
> +	if (!mbox)
> +		return -ENOMEM;
> +
> +	exynos_acpm->regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(exynos_acpm->regs))
> +		return PTR_ERR(exynos_acpm->regs);
> +
> +	shmem = of_parse_phandle(node, "shmem", 0);
> +	ret = of_address_to_resource(shmem, 0, &res);
> +	of_node_put(shmem);
> +	if (ret) {
> +		dev_err(dev, "Failed to get shared memory.\n");
> +		return ret;
> +	}
> +
> +	size = resource_size(&res);
> +	exynos_acpm->sram_base = devm_ioremap(dev, res.start, size);
> +	if (!exynos_acpm->sram_base) {
> +		dev_err(dev, "Failed to ioremap shared memory.\n");
> +		return -ENOMEM;
> +	}
> +
> +	prop = of_get_property(node, "initdata-base", NULL);
> +	if (!prop) {
> +		dev_err(dev, "Parsing initdata_base failed.\n");
> +		return -EINVAL;
> +	}
> +
> +	exynos_acpm->pclk = devm_clk_get(dev, "pclk");
> +	if (IS_ERR(exynos_acpm->pclk)) {
> +		dev_err(dev, "Missing peripheral clock.\n");
> +		return PTR_ERR(exynos_acpm->pclk);
> +	}
> +
> +	ret = clk_prepare_enable(exynos_acpm->pclk);
> +	if (ret) {
> +		dev_err(dev, "Failed to enable the peripheral clock.\n");
> +		return ret;
> +	}
> +
> +	exynos_acpm->wq = alloc_workqueue("exynos-acpm-wq", 0, 0);
> +	if (!exynos_acpm->wq)
> +		return -ENOMEM;
> +
> +	exynos_acpm->dev = dev;
> +	exynos_acpm->mbox = mbox;
> +	exynos_acpm->shmem = exynos_acpm->sram_base +
> be32_to_cpup(prop);
> +
> +	ret = exynos_acpm_chans_init(exynos_acpm);
> +	if (ret)
> +		return ret;
> +
> +	mbox->num_chans = exynos_acpm->num_chans;
> +	mbox->dev = dev;
> +	mbox->ops = &exynos_acpm_chan_ops;
> +
> +	platform_set_drvdata(pdev, exynos_acpm);
> +
> +	/* Mask out all interrupts. We support just polling channels for
now.
> */
> +	writel_relaxed(EXYNOS_ACPM_INTMR0_MASK,
> +		       exynos_acpm->regs + EXYNOS_ACPM_INTMR0);
> +
> +	ret = devm_mbox_controller_register(dev, mbox);
> +	if (ret)
> +		dev_err(dev, "Failed to register mbox_controller(%d).\n",
> ret);
> +
> +	return ret;
> +}
> +
> +static void exynos_acpm_remove(struct platform_device *pdev) {
> +	struct exynos_acpm *exynos_acpm = platform_get_drvdata(pdev);
> +
> +	flush_workqueue(exynos_acpm->wq);
> +	destroy_workqueue(exynos_acpm->wq);
> +	clk_disable_unprepare(exynos_acpm->pclk);
> +}
> +
> +static struct platform_driver exynos_acpm_driver = {
> +	.probe	= exynos_acpm_probe,
> +	.remove = exynos_acpm_remove,
> +	.driver	= {
> +		.name = "exynos-acpm",
> +		.owner	= THIS_MODULE,
> +		.of_match_table	= exynos_acpm_match,
> +	},
> +};
> +module_platform_driver(exynos_acpm_driver);
> +
> +MODULE_AUTHOR("Tudor Ambarus <tudor.ambarus@xxxxxxxxxx>");
> +MODULE_DESCRIPTION("EXYNOS ACPM mailbox driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mailbox/exynos-acpm-message.h
> b/include/linux/mailbox/exynos-acpm-message.h
> new file mode 100644
> index 000000000000..3799868c40b8
> --- /dev/null
> +++ b/include/linux/mailbox/exynos-acpm-message.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright 2024 Linaro Ltd.
> + */
> +
> +#ifndef _LINUX_EXYNOS_ACPM_MESSAGE_H_
> +#define _LINUX_EXYNOS_ACPM_MESSAGE_H_
> +
> +#include <linux/types.h>
> +
> +/**
> + * struct exynos_acpm_message - exynos ACPM mailbox message format.
> + * @cmd: pointer to u32 command.
> + * @len: length of the command.
> + */
> +struct exynos_acpm_message {
> +	u32 *cmd;
> +	size_t len;
> +};
> +
> +#endif /* _LINUX_EXYNOS_ACPM_MESSAGE_H_ */
> --
> 2.47.0.rc1.288.g06298d1525-goog






[Index of Archives]     [Linux SoC Development]     [Linux Rockchip Development]     [Linux for Synopsys ARC Processors]    
  • [Linux on Unisoc (RDA Micro) SoCs]     [Linux Actions SoC]     [Linux USB Development]     [Video for Linux]     [Linux Audio Users]     [Linux SCSI]     [Yosemite News]

  •   Powered by Linux