Re: [PATCH v5 2/3] firmware: add Exynos ACPM protocol driver

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

 



Hi, Krzysztof,

Thanks for the review!

On 12/24/24 2:14 PM, Krzysztof Kozlowski wrote:
> On 20/12/2024 15:32, Tudor Ambarus wrote:
>> diff --git a/drivers/firmware/samsung/Kconfig b/drivers/firmware/samsung/Kconfig
>> new file mode 100644
>> index 000000000000..750b41342174
>> --- /dev/null
>> +++ b/drivers/firmware/samsung/Kconfig
>> @@ -0,0 +1,14 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config EXYNOS_ACPM_PROTOCOL
>> +	tristate "Exynos Alive Clock and Power Manager (ACPM) Message Protocol"
>> +	depends on ARCH_EXYNOS || COMPILE_TEST
>> +	depends on EXYNOS_MBOX
> 
> Is it build time dependency? No || COMPILE_TEST?

There's no build time dependency, I'll drop this line.
> 
> Is it fine when EXYNOS_MBOX is a module?

Yes. When the EXYNOS_MBOX module is not loaded, and one tries to load
EXYNOS_ACPM_PROTOCOL module, the later will defer probe when requesting
the mailbox channels, but that's fine.

cut

>> diff --git a/drivers/firmware/samsung/exynos-acpm-pmic.c b/drivers/firmware/samsung/exynos-acpm-pmic.c

cut

>> +static void acpm_pmic_init_bulk_write_cmd(u32 *cmd, u8 type, u8 reg, u8 chan,
>> +					  u8 count, u8 *buf)
> 
> u32 cmd[4] - I think newer GCC could use of that. At least reader will know.
> 
> const u8 *buf
> 
> Same in all other places, where applicable
> 
Indeed, will update.

cut
>> +int acpm_pmic_update_reg(const struct acpm_handle *handle, int acpm_chan_id,
>> +			 u8 type, u8 reg, u8 chan, u8 value, u8 mask)
>> +{
>> +	struct acpm_xfer xfer;
>> +	u32 cmd[4] = {0};
>> +	int ret;
>> +
>> +	acpm_pmic_init_update_cmd(cmd, type, reg, chan, value, mask);
>> +	acpm_dvfs_set_xfer(&xfer, cmd, acpm_chan_id);
>> +
>> +	ret = acpm_do_xfer(handle, &xfer);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return FIELD_GET(ACPM_PMIC_RETURN, xfer.rx.cmd[1]);
>> +}
> 
> I have troubles understanding the split. I would assume PMIC uses the
> ACPM to talk with PMIC... or somehow the opposite, but here this is all
> mixed. You have acpm_pmic_update_reg() which uses  ACPM code
> (acpm_do_xfer()), but ACPM code also references acpm_pmic_update_reg()
> and others. This is a circular dependency between objects, which
> compiles and works fine but is confusing.
> 
This is related to the public header comment, I'll respond there on the
overall driver architecture.

>> diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c

cut

>> +#include <linux/of_address.h>
> 
> This looks unused

it's necessary for of_address_to_resource()

cut

>> +static inline void acpm_memcpy_fromio32(void *to, const void __iomem *from,
>> +					size_t count)
>> +{
>> +	WARN_ON(!IS_ALIGNED((unsigned long)from, 4) ||
>> +		!IS_ALIGNED((unsigned long)to, 4) ||
>> +		count % 4);
> 
> These should be build time checks if this is necessary.
> 

The method is called at runtime. Right now unaligned accesses can not
happen because the only protocol implemented (PMIC), uses on stack u32
buffers. The checks are just to avoid potential issues if future
protocols will implement things in an unaligned fashion. I can drop the
checks if you feel I was over cautious.

>> +
>> +	__ioread32_copy(to, from, count / 4);
>> +}

cut

>> +int acpm_do_xfer(const struct acpm_handle *handle, struct acpm_xfer *xfer)
> 
> const xfer, so it is clear that caller keeps ownership and must ensure
> it is valid memory through entire xfer time.

will fix.

> 
>> +{
>> +	struct acpm_info *acpm = handle_to_acpm_info(handle);
>> +	struct acpm_chan *achan = &acpm->chans[xfer->acpm_chan_id];
>> +	struct acpm_msg *tx = &xfer->tx;
> 
> Looks like const, same for function argument xfer

yes, will fix.

> 
>> +	u32 idx, tx_front;
>> +	int ret;
>> +
>> +	if (!tx->cmd || tx->len > achan->mlen || xfer->rx.len > achan->mlen)
>> +		return -EINVAL;
>> +
>> +	if (!achan->poll_completion) {
>> +		dev_err(achan->acpm->dev, "Interrupt mode not supported\n");
>> +		return -EOPNOTSUPP;
>> +	}
>> +
>> +	mutex_lock(&achan->tx_lock);
>> +
>> +	tx_front = readl(achan->tx.front);
>> +	idx = (tx_front + 1) % achan->qlen;
>> +
>> +	ret = acpm_wait_for_queue_slots(achan, idx);
>> +	if (ret) {
>> +		mutex_unlock(&achan->tx_lock);
>> +		return ret;
>> +	}
>> +
>> +	acpm_prepare_xfer(achan, xfer);
>> +
>> +	/* Write TX command. */
>> +	acpm_memcpy_toio32(achan->tx.base + achan->mlen * tx_front, tx->cmd,
>> +			   tx->len);
>> +
>> +	/* Advance TX front. */
>> +	writel(idx, achan->tx.front);
>> +
>> +	mutex_unlock(&achan->tx_lock);
> 
> Just to be sure I understand correctly:
> If concurrent transfer happened exactly now, it would use incremented
> tx_front, thus it would not overwrite data written here, right?

right

cut
>> +static int acpm_put_handle(const struct acpm_handle *handle)
>> +{
>> +	struct acpm_info *acpm;
>> +
>> +	if (IS_ERR(handle))
>> +		return PTR_ERR(handle);
>> +	if (!handle)
>> +		return -EINVAL;
> 
> I think this cannot happen and you should rather choose: either NULL is
> allowed or PTR_ERR, but not both. Although in few cases both ERR and
> NULL are desired, so write a comment why here.

both checks are unnecessary, I'll get rid of them altogether. The method
is called just by devm_acpm_release, and at that point the pointer to
handle is valid.
> 
>> +
>> +	acpm = handle_to_acpm_info(handle);
>> +	mutex_lock(&acpm_list_mutex);
>> +	if (!WARN_ON(!acpm->users))
>> +		acpm->users--;
> 
> Use refcnt
> 
okay

cut

>> + */
>> +static const struct acpm_handle *acpm_get_by_phandle(struct device_node *np,
>> +						     const char *property)
>> +{
>> +	struct acpm_handle *handle = NULL;
>> +	struct device_node *acpm_np;
>> +	struct acpm_info *info;
>> +
>> +	if (!np) {
>> +		pr_err("I need a device pointer\n");
>> +		return ERR_PTR(-EINVAL);
>> +	}
>> +
>> +	acpm_np = of_parse_phandle(np, property, 0);
>> +	if (!acpm_np)
>> +		return ERR_PTR(-ENODEV);
>> +
>> +	mutex_lock(&acpm_list_mutex);
>> +	list_for_each_entry(info, &acpm_list, node) {
>> +		if (acpm_np == info->dev->of_node) {
>> +			handle = &info->handle;
>> +			info->users++;
>> +			break;
>> +		}
>> +	}
>> +	mutex_unlock(&acpm_list_mutex);
>> +	of_node_put(acpm_np);
>> +
> 
> You also need device links and probably try_module_get. See clk.c
> clk_hw_create_clk() or of_qcom_ice_get(). Interestingly, none of them
> perform both operations, which I think is necessary.
> 
> I think you could also avoid entire list and mutex by using
> platform_get_drvdata(), see of_qcom_ice_get().
> 

I'll take a look, thanks for the pointers!

cut

>> +#endif /* __EXYNOS_ACPM_H__ */
>> diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
>> new file mode 100644
>> index 000000000000..f834af20cef8
>> --- /dev/null
>> +++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
>> @@ -0,0 +1,55 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Copyright 2020 Samsung Electronics Co., Ltd.
>> + * Copyright 2020 Google LLC.
>> + * Copyright 2024 Linaro Ltd.
>> + */
>> +
>> +#ifndef __EXYNOS_ACPM_PROTOCOL_H
>> +#define __EXYNOS_ACPM_PROTOCOL_H
>> +
>> +#include <linux/types.h>
>> +
>> +struct acpm_msg {
>> +	u32 *cmd;
> 
> const most likely, although then you have different message for tx and
> tx. Not sure if there is a benefit of having it as structure.

tx shall be const indeed. I guess I can get rid of the struct and have
const u32 *tx, and u32 *rx, while referencing the same memory.

> 
>> +	size_t len;
>> +};
>> +
>> +struct acpm_xfer {
>> +	struct acpm_msg tx;
>> +	struct acpm_msg rx;
>> +	int acpm_chan_id;
>> +};
>> +
>> +struct acpm_handle;
>> +
>> +struct acpm_pmic_ops {
>> +	int (*read_reg)(const struct acpm_handle *handle, int acpm_chan_id,
>> +			u8 type, u8 reg, u8 chan, u8 *dest);
>> +	int (*bulk_read)(const struct acpm_handle *handle, int acpm_chan_id,
>> +			 u8 type, u8 reg, u8 chan, u8 count, u8 *buf);
>> +	int (*write_reg)(const struct acpm_handle *handle, int acpm_chan_id,
>> +			 u8 type, u8 reg, u8 chan, u8 value);
>> +	int (*bulk_write)(const struct acpm_handle *handle, int acpm_chan_id,
>> +			  u8 type, u8 reg, u8 chan, u8 count, u8 *buf);
>> +	int (*update_reg)(const struct acpm_handle *handle, int acpm_chan_id,
>> +			  u8 type, u8 reg, u8 chan, u8 value, u8 mask);
>> +};
>> +
>> +struct acpm_ops {
>> +	struct acpm_pmic_ops pmic_ops;
>> +};
>> +
>> +/**
>> + * struct acpm_handle - Reference to an initialized protocol instance
>> + * @ops:
>> + */
>> +struct acpm_handle {
>> +	struct acpm_ops ops;
>> +};
> 
> All above should not be in public header but private to the driver. In
> public header you expose things available to consumers.... which there
> are no? So entire header can be next to the driver.

This can be trimmed a little bit, acpm_xfer shall be private indeed.

The acpm_handle and the ops will be public. The consumers use
devm_acpm_get_by_phandle() to get a pointer to the acpm_handle, and then
use the ops (PMIC, DVFS, etc.) exposed by the handle.

We have:
1/	drivers/firmware/samsung/exynos-acpm-{pmic,dvfs,etc.}.c
These are the protocols exposed by the ACPM interface. The operations
for a protocol are declared in:
	drivers/firmware/samsung/exynos-acpm-{pmic, dvfs, etc}.h

The protocol op methods construct a message in the ACPM format using the
args received from the consumers and then call acpm_do_xfer() to
transfer a message.

2/	drivers/firmware/samsung/exynos-acpm.c

This is the ACPM interface driver. It manages the xfers. It saves all
the protocols operations in struct acpm_handle and provides the means
for the consumers to get a pointer to it.

3/ consumer drivers - these will obtain a pointer to the acpm_handle and
use the ops exposed there. Here's an example of such a consumer using
similar infrastructure. The consumer gets a pointer to the interface handle:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/reset/reset-ti-sci.c?h=v6.13-rc5#n221
then uses the ops exposed there:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/reset/reset-ti-sci.c?h=v6.13-rc5#n78


Cheers, and Happy New Year! :)
ta




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux