diff mbox series

[1/3] hwspinlock: sunxi: Implement support for Allwinner's A64 SoC

Message ID 20200210170143.20007-2-nborisov@suse.com (mailing list archive)
State New, archived
Headers show
Series Add support for hwspinlock on A64 SoC | expand

Commit Message

Nikolay Borisov Feb. 10, 2020, 5:01 p.m. UTC
Based on the datasheet this implements support for the hwspinlock IP
block.

Signed-off-by: Nikolay Borisov <nborisov@suse.com>
---
 drivers/hwspinlock/Kconfig            |   9 ++
 drivers/hwspinlock/Makefile           |   1 +
 drivers/hwspinlock/sunxi_hwspinlock.c | 181 ++++++++++++++++++++++++++
 3 files changed, 191 insertions(+)
 create mode 100644 drivers/hwspinlock/sunxi_hwspinlock.c

Comments

Bjorn Andersson Feb. 10, 2020, 6:57 p.m. UTC | #1
On Mon 10 Feb 09:01 PST 2020, Nikolay Borisov wrote:
[..]
> diff --git a/drivers/hwspinlock/sunxi_hwspinlock.c b/drivers/hwspinlock/sunxi_hwspinlock.c
> new file mode 100644
> index 000000000000..8e5685357fbf
> --- /dev/null
> +++ b/drivers/hwspinlock/sunxi_hwspinlock.c
> @@ -0,0 +1,181 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Author: Nikolay Borisov <nborisov@suse.com> */
> +
> +#include <linux/clk.h>
> +#include <linux/reset.h>
> +#include <linux/hwspinlock.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>

Please sort these.

> +
> +#include "hwspinlock_internal.h"
> +
> +/* Spinlock register offsets */
> +#define LOCK_BASE_OFFSET		0x0100
> +
> +#define SPINLOCK_NUMLOCKS_BIT_OFFSET	(28)
> +/* Possible values of SPINLOCK_LOCK_REG */
> +#define SPINLOCK_NOTTAKEN		(0)	/* free */
> +#define SPINLOCK_TAKEN			(1)	/* locked */
> +
> +struct sunxi_hwspinlock {
> +	struct clk *clk;
> +	struct reset_control *reset;
> +	struct hwspinlock_device bank;
> +};
> +
> +static int sunxi_hwspinlock_trylock(struct hwspinlock *lock)
> +{
> +	void __iomem *lock_addr = lock->priv;
> +
> +	/* attempt to acquire the lock by reading its value */
> +	return (SPINLOCK_NOTTAKEN == readl(lock_addr));

Please drop the parenthesis and flip the expression around, i.e.
variable == constant.

> +}
> +
> +static void sunxi_hwspinlock_unlock(struct hwspinlock *lock)
> +{
> +	void __iomem *lock_addr = lock->priv;
> +
> +	/* release the lock by writing 0 to it */
> +	writel(SPINLOCK_NOTTAKEN, lock_addr);
> +}
> +
> +static const struct hwspinlock_ops sunxi_hwspinlock_ops = {
> +	.trylock	= sunxi_hwspinlock_trylock,
> +	.unlock		= sunxi_hwspinlock_unlock,
> +};
> +
> +static int sunxi_get_num_locks(void __iomem *io_base)
> +{
> +	int i = readl(io_base);
> +	i >>= SPINLOCK_NUMLOCKS_BIT_OFFSET;

Please make i u32.

> +
> +	switch (i) {
> +	case 0x1: return 32;
> +	case 0x2: return 64;
> +	case 0x3: return 128;
> +	case 0x4: return 256;
> +	}
> +
> +	return 0;
> +}
> +
> +static int sunxi_hwspinlock_probe(struct platform_device *pdev)
> +{
> +	struct sunxi_hwspinlock *hw;
> +	void __iomem *io_base;
> +	struct resource *res;
> +	struct clk *clk;
> +	struct reset_control *reset;
> +	int i, ret, num_locks;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	io_base = devm_ioremap_resource(&pdev->dev, res);

Please use devm_platform_ioremap_resource()

> +	if (IS_ERR(io_base))
> +		return PTR_ERR(io_base);
> +
> +	/*
> +	 * make sure the module is enabled and clocked before reading
> +	 * the module SYSSTATUS register
> +	 */
> +	clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +
> +	ret = clk_prepare_enable(clk);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Cannot enable clock\n");
> +		return ret;
> +	}
> +
> +	/* Disable soft reset */
> +        reset= devm_reset_control_get_exclusive(&pdev->dev, NULL);
> +        if (IS_ERR(reset)) {
> +                ret = PTR_ERR(reset);
> +                goto out_declock;
> +        }
> +        reset_control_deassert(reset);

Indentation of this chunk looks off.

> +
> +	num_locks = sunxi_get_num_locks(io_base);
> +	if (!num_locks) {
> +		dev_err(&pdev->dev, "Unrecognised sunxi hwspinlock device\n");
> +		ret = -EINVAL;
> +		goto out_reset;
> +	}
> +
> +	hw = devm_kzalloc(&pdev->dev, sizeof(*hw) +
> +			  num_locks * sizeof(struct hwspinlock), GFP_KERNEL);

Please use struct_size() to calculate the size here.

> +	if (!hw) {
> +		ret = -ENOMEM;
> +		goto out_reset;
> +	}
> +
> +	hw->clk = clk;
> +	hw->reset = reset;
> +
> +	io_base += LOCK_BASE_OFFSET;
> +	for (i = 0; i < num_locks; i++)
> +		hw->bank.lock[i].priv = io_base + i * sizeof(u32);
> +
> +	platform_set_drvdata(pdev, hw);
> +
> +	ret = hwspin_lock_register(&hw->bank, &pdev->dev, &sunxi_hwspinlock_ops,
> +				   0, num_locks);

People will likely send patches to replace this with
devm_hwspin_lock_register(), but that will create an invalid ordering
between the clock disable, reset assert and the hwspinlock
unregistration.

You could deal with this using devm_add_action() and
devm_add_action_or_reset() for the clock and reset above. That will save
us future churn, would clean up your error handling and you could drop
the remove function completely.

> +
> +	if (!ret)
> +		return ret;
> +out_reset:
> +	reset_control_assert(reset);
> +out_declock:
> +	clk_disable_unprepare(clk);
> +	return ret;
> +}
> +
> +static int sunxi_hwspinlock_remove(struct platform_device *pdev)
> +{
> +	struct sunxi_hwspinlock *hw = platform_get_drvdata(pdev);
> +	int ret;
> +
> +	ret = hwspin_lock_unregister(&hw->bank);
> +	if (ret)
> +		dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret);
> +
> +	reset_control_assert(hw->reset);
> +	clk_disable_unprepare(hw->clk);
> +
> +	return 0;
> +}
> +

Regards,
Bjorn
Nikolay Borisov Feb. 10, 2020, 7:06 p.m. UTC | #2
On 10.02.20 г. 20:57 ч., Bjorn Andersson wrote:
> On Mon 10 Feb 09:01 PST 2020, Nikolay Borisov wrote:
> [..]
>> diff --git a/drivers/hwspinlock/sunxi_hwspinlock.c b/drivers/hwspinlock/sunxi_hwspinlock.c
>> new file mode 100644
>> index 000000000000..8e5685357fbf
>> --- /dev/null
>> +++ b/drivers/hwspinlock/sunxi_hwspinlock.c
>> @@ -0,0 +1,181 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/* Author: Nikolay Borisov <nborisov@suse.com> */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/reset.h>
>> +#include <linux/hwspinlock.h>
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
> 
> Please sort these.

alphabetically or reverse christmas tree?

> 
>> +

<snip>
>> +	hw->clk = clk;
>> +	hw->reset = reset;
>> +
>> +	io_base += LOCK_BASE_OFFSET;
>> +	for (i = 0; i < num_locks; i++)
>> +		hw->bank.lock[i].priv = io_base + i * sizeof(u32);
>> +
>> +	platform_set_drvdata(pdev, hw);
>> +
>> +	ret = hwspin_lock_register(&hw->bank, &pdev->dev, &sunxi_hwspinlock_ops,
>> +				   0, num_locks);
> 
> People will likely send patches to replace this with
> devm_hwspin_lock_register(), but that will create an invalid ordering
> between the clock disable, reset assert and the hwspinlock
> unregistration.
> 
> You could deal with this using devm_add_action() and
> devm_add_action_or_reset() for the clock and reset above. That will save
> us future churn, would clean up your error handling and you could drop
> the remove function completely.

This is my first rodeo in device driver land so I'm learning. It looks
like what you want here is similar to what there is in
sprd_hwspinlock.c. Will rework it.

> 
>> +
>> +	if (!ret)
>> +		return ret;
>> +out_reset:
>> +	reset_control_assert(reset);
>> +out_declock:
>> +	clk_disable_unprepare(clk);
>> +	return ret;
>> +}
>> +
>> +static int sunxi_hwspinlock_remove(struct platform_device *pdev)
>> +{
>> +	struct sunxi_hwspinlock *hw = platform_get_drvdata(pdev);
>> +	int ret;
>> +
>> +	ret = hwspin_lock_unregister(&hw->bank);
>> +	if (ret)
>> +		dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret);
>> +
>> +	reset_control_assert(hw->reset);
>> +	clk_disable_unprepare(hw->clk);
>> +
>> +	return 0;
>> +}
>> +
> 
> Regards,
> Bjorn
>
Bjorn Andersson Feb. 10, 2020, 8:05 p.m. UTC | #3
On Mon 10 Feb 11:06 PST 2020, Nikolay Borisov wrote:

> 
> 
> On 10.02.20 ??. 20:57 ??., Bjorn Andersson wrote:
> > On Mon 10 Feb 09:01 PST 2020, Nikolay Borisov wrote:
> > [..]
> >> diff --git a/drivers/hwspinlock/sunxi_hwspinlock.c b/drivers/hwspinlock/sunxi_hwspinlock.c
> >> new file mode 100644
> >> index 000000000000..8e5685357fbf
> >> --- /dev/null
> >> +++ b/drivers/hwspinlock/sunxi_hwspinlock.c
> >> @@ -0,0 +1,181 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +/* Author: Nikolay Borisov <nborisov@suse.com> */
> >> +
> >> +#include <linux/clk.h>
> >> +#include <linux/reset.h>
> >> +#include <linux/hwspinlock.h>
> >> +#include <linux/io.h>
> >> +#include <linux/kernel.h>
> >> +#include <linux/module.h>
> >> +#include <linux/of.h>
> >> +#include <linux/platform_device.h>
> > 
> > Please sort these.
> 
> alphabetically or reverse christmas tree?
> 

Alphabetically please.

> > 
> >> +
> 
> <snip>
> >> +	hw->clk = clk;
> >> +	hw->reset = reset;
> >> +
> >> +	io_base += LOCK_BASE_OFFSET;
> >> +	for (i = 0; i < num_locks; i++)
> >> +		hw->bank.lock[i].priv = io_base + i * sizeof(u32);
> >> +
> >> +	platform_set_drvdata(pdev, hw);
> >> +
> >> +	ret = hwspin_lock_register(&hw->bank, &pdev->dev, &sunxi_hwspinlock_ops,
> >> +				   0, num_locks);
> > 
> > People will likely send patches to replace this with
> > devm_hwspin_lock_register(), but that will create an invalid ordering
> > between the clock disable, reset assert and the hwspinlock
> > unregistration.
> > 
> > You could deal with this using devm_add_action() and
> > devm_add_action_or_reset() for the clock and reset above. That will save
> > us future churn, would clean up your error handling and you could drop
> > the remove function completely.
> 
> This is my first rodeo in device driver land so I'm learning. It looks
> like what you want here is similar to what there is in
> sprd_hwspinlock.c. Will rework it.
> 

Exactly like that, forgot that we had an example of this in the sprd
driver. That will ensure that we rely solely on devres to unroll the
resources in a suitable order.

Regards,
Bjorn

> > 
> >> +
> >> +	if (!ret)
> >> +		return ret;
> >> +out_reset:
> >> +	reset_control_assert(reset);
> >> +out_declock:
> >> +	clk_disable_unprepare(clk);
> >> +	return ret;
> >> +}
> >> +
> >> +static int sunxi_hwspinlock_remove(struct platform_device *pdev)
> >> +{
> >> +	struct sunxi_hwspinlock *hw = platform_get_drvdata(pdev);
> >> +	int ret;
> >> +
> >> +	ret = hwspin_lock_unregister(&hw->bank);
> >> +	if (ret)
> >> +		dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret);
> >> +
> >> +	reset_control_assert(hw->reset);
> >> +	clk_disable_unprepare(hw->clk);
> >> +
> >> +	return 0;
> >> +}
> >> +
> > 
> > Regards,
> > Bjorn
> >
Maxime Ripard Feb. 11, 2020, 7:46 a.m. UTC | #4
Hi,

On Mon, Feb 10, 2020 at 07:01:41PM +0200, Nikolay Borisov wrote:
> Based on the datasheet this implements support for the hwspinlock IP
> block.

How was this tested?

There's also a lot of checkpatch issues, make sure you fix those.

> Signed-off-by: Nikolay Borisov <nborisov@suse.com>
> ---
>  drivers/hwspinlock/Kconfig            |   9 ++
>  drivers/hwspinlock/Makefile           |   1 +
>  drivers/hwspinlock/sunxi_hwspinlock.c | 181 ++++++++++++++++++++++++++
>  3 files changed, 191 insertions(+)
>  create mode 100644 drivers/hwspinlock/sunxi_hwspinlock.c
>
> diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig
> index 37740e992cfa..ebc1ea48ef16 100644
> --- a/drivers/hwspinlock/Kconfig
> +++ b/drivers/hwspinlock/Kconfig
> @@ -68,3 +68,12 @@ config HSEM_U8500
>  	  SoC.
>
>  	  If unsure, say N.
> +
> +config HWSPINLOCK_SUNXI
> +	tristate "Allwinner Hardware Spinlock device"
> +	depends on ARCH_SUNXI
> +	depends on HWSPINLOCK
> +	help
> +	  Say y here to support the SUNXI Hardware Spinlock device.
> +
> +	  If unsure, say N.

sunxi doesn't really mean anything though, the A10 is also part of the
sunxi family and doesn't have that IP. Similarly, nothing prevents a
future SoC from changing that design. The first SoC that used it was
the A33 iirc, so let's just use sun8i.

> diff --git a/drivers/hwspinlock/Makefile b/drivers/hwspinlock/Makefile
> index ed053e3f02be..6be5ebef906e 100644
> --- a/drivers/hwspinlock/Makefile
> +++ b/drivers/hwspinlock/Makefile
> @@ -8,5 +8,6 @@ obj-$(CONFIG_HWSPINLOCK_OMAP)		+= omap_hwspinlock.o
>  obj-$(CONFIG_HWSPINLOCK_QCOM)		+= qcom_hwspinlock.o
>  obj-$(CONFIG_HWSPINLOCK_SIRF)		+= sirf_hwspinlock.o
>  obj-$(CONFIG_HWSPINLOCK_SPRD)		+= sprd_hwspinlock.o
> +obj-$(CONFIG_HWSPINLOCK_SUNXI)		+= sunxi_hwspinlock.o

Ditto for the name of the file (and basically every symbol in your
driver.

>  obj-$(CONFIG_HWSPINLOCK_STM32)		+= stm32_hwspinlock.o
>  obj-$(CONFIG_HSEM_U8500)		+= u8500_hsem.o
> diff --git a/drivers/hwspinlock/sunxi_hwspinlock.c b/drivers/hwspinlock/sunxi_hwspinlock.c
> new file mode 100644
> index 000000000000..8e5685357fbf
> --- /dev/null
> +++ b/drivers/hwspinlock/sunxi_hwspinlock.c
> @@ -0,0 +1,181 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Author: Nikolay Borisov <nborisov@suse.com> */
> +
> +#include <linux/clk.h>
> +#include <linux/reset.h>
> +#include <linux/hwspinlock.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +
> +#include "hwspinlock_internal.h"
> +
> +/* Spinlock register offsets */
> +#define LOCK_BASE_OFFSET		0x0100
> +
> +#define SPINLOCK_NUMLOCKS_BIT_OFFSET	(28)
> +/* Possible values of SPINLOCK_LOCK_REG */
> +#define SPINLOCK_NOTTAKEN		(0)	/* free */
> +#define SPINLOCK_TAKEN			(1)	/* locked */
> +
> +struct sunxi_hwspinlock {
> +	struct clk *clk;
> +	struct reset_control *reset;
> +	struct hwspinlock_device bank;
> +};
> +
> +static int sunxi_hwspinlock_trylock(struct hwspinlock *lock)
> +{
> +	void __iomem *lock_addr = lock->priv;
> +
> +	/* attempt to acquire the lock by reading its value */
> +	return (SPINLOCK_NOTTAKEN == readl(lock_addr));
> +}
> +
> +static void sunxi_hwspinlock_unlock(struct hwspinlock *lock)
> +{
> +	void __iomem *lock_addr = lock->priv;
> +
> +	/* release the lock by writing 0 to it */
> +	writel(SPINLOCK_NOTTAKEN, lock_addr);
> +}
> +
> +static const struct hwspinlock_ops sunxi_hwspinlock_ops = {
> +	.trylock	= sunxi_hwspinlock_trylock,
> +	.unlock		= sunxi_hwspinlock_unlock,
> +};
> +
> +static int sunxi_get_num_locks(void __iomem *io_base)
> +{
> +	int i = readl(io_base);

readl returns a u32.

> +	i >>= SPINLOCK_NUMLOCKS_BIT_OFFSET;

And you probably want to mask the value to avoid other bitfields in
the register from messing with that value. FIELD_GET will help you to
do so.

> +	switch (i) {
> +	case 0x1: return 32;
> +	case 0x2: return 64;
> +	case 0x3: return 128;
> +	case 0x4: return 256;
> +	}
> +
> +	return 0;
> +}
> +
> +static int sunxi_hwspinlock_probe(struct platform_device *pdev)
> +{
> +	struct sunxi_hwspinlock *hw;
> +	void __iomem *io_base;
> +	struct resource *res;
> +	struct clk *clk;
> +	struct reset_control *reset;
> +	int i, ret, num_locks;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	io_base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(io_base))
> +		return PTR_ERR(io_base);
> +
> +	/*
> +	 * make sure the module is enabled and clocked before reading
> +	 * the module SYSSTATUS register
> +	 */

You don't define that register anywhere?

> +	clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +
> +	ret = clk_prepare_enable(clk);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Cannot enable clock\n");
> +		return ret;
> +	}

Can't we do that with runtime_pm?

> +	/* Disable soft reset */
> +        reset= devm_reset_control_get_exclusive(&pdev->dev, NULL);
> +        if (IS_ERR(reset)) {
> +                ret = PTR_ERR(reset);
> +                goto out_declock;
> +        }
> +        reset_control_deassert(reset);

We might have the same issue than the mailbox driver where the
firmware will need to access the block at any time, so we can't really
toggle the reset line as we want.

> +	num_locks = sunxi_get_num_locks(io_base);
> +	if (!num_locks) {
> +		dev_err(&pdev->dev, "Unrecognised sunxi hwspinlock device\n");
> +		ret = -EINVAL;
> +		goto out_reset;
> +	}
> +
> +	hw = devm_kzalloc(&pdev->dev, sizeof(*hw) +
> +			  num_locks * sizeof(struct hwspinlock), GFP_KERNEL);
> +	if (!hw) {
> +		ret = -ENOMEM;
> +		goto out_reset;
> +	}

That looks rather convoluted (especially since the variable length
array is at the second level), and can be made more obvious by:

- Removing the hwspinlock_device from sunxi_hwspinlock and allocating
  both separately.

- And then allocate the hwspinlock_device separately with struct_size

> +	hw->clk = clk;
> +	hw->reset = reset;

Why not using the structure directly instead of having temporary
variables?

> +	io_base += LOCK_BASE_OFFSET;
> +	for (i = 0; i < num_locks; i++)
> +		hw->bank.lock[i].priv = io_base + i * sizeof(u32);

Using a define for the registers offset would be nice here.

> +	platform_set_drvdata(pdev, hw);
> +
> +	ret = hwspin_lock_register(&hw->bank, &pdev->dev, &sunxi_hwspinlock_ops,
> +				   0, num_locks);
> +
> +	if (!ret)
> +		return ret;

That's a slightly weird construct too, since in pretty much all the
driver you return early on error and here you return early on
success. Just return ret if there's an error just like you're doing
everywhere else, and return 0 after that.

> +out_reset:
> +	reset_control_assert(reset);
> +out_declock:
> +	clk_disable_unprepare(clk);
> +	return ret;
> +}
> +
> +static int sunxi_hwspinlock_remove(struct platform_device *pdev)
> +{
> +	struct sunxi_hwspinlock *hw = platform_get_drvdata(pdev);
> +	int ret;
> +
> +	ret = hwspin_lock_unregister(&hw->bank);
> +	if (ret)
> +		dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret);
> +
> +	reset_control_assert(hw->reset);
> +	clk_disable_unprepare(hw->clk);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id sunxi_hwpinlock_ids[] = {
> +	{ .compatible = "allwinner,sun50i-a64-hwspinlock", },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, sunxi_hwpinlock_ids);
> +
> +static struct platform_driver sunxi_hwspinlock_driver = {
> +	.probe		= sunxi_hwspinlock_probe,
> +	.remove		= sunxi_hwspinlock_remove,
> +	.driver		= {
> +		.name	= "sunxi_hwspinlock",
> +		.of_match_table = sunxi_hwpinlock_ids,
> +	},
> +};
> +
> +static int __init sunxi_hwspinlock_init(void)
> +{
> +	return platform_driver_register(&sunxi_hwspinlock_driver);
> +}
> +/* board init code might need to reserve hwspinlocks for predefined purposes */
> +postcore_initcall(sunxi_hwspinlock_init);

We don't have any board init code. Can't we just use a regular
module_platform_driver call here?

Thanks!
Maxime
Nikolay Borisov Feb. 11, 2020, 8:08 a.m. UTC | #5
On 11.02.20 г. 9:46 ч., Maxime Ripard wrote:
> Hi,
> 
> On Mon, Feb 10, 2020 at 07:01:41PM +0200, Nikolay Borisov wrote:
>> Based on the datasheet this implements support for the hwspinlock IP
>> block.
> 
> How was this tested?

I tested it on my pine64 lts e.g. loading the driver and reading the
reset/clock/sysstatus registers to ensure everything is unmasked and has
expected values.

> 
> There's also a lot of checkpatch issues, make sure you fix those.
> 
>> Signed-off-by: Nikolay Borisov <nborisov@suse.com>
>> ---
>>  drivers/hwspinlock/Kconfig            |   9 ++
>>  drivers/hwspinlock/Makefile           |   1 +
>>  drivers/hwspinlock/sunxi_hwspinlock.c | 181 ++++++++++++++++++++++++++
>>  3 files changed, 191 insertions(+)
>>  create mode 100644 drivers/hwspinlock/sunxi_hwspinlock.c
>>
>> diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig
>> index 37740e992cfa..ebc1ea48ef16 100644
>> --- a/drivers/hwspinlock/Kconfig
>> +++ b/drivers/hwspinlock/Kconfig
>> @@ -68,3 +68,12 @@ config HSEM_U8500
>>  	  SoC.
>>
>>  	  If unsure, say N.
>> +
>> +config HWSPINLOCK_SUNXI
>> +	tristate "Allwinner Hardware Spinlock device"
>> +	depends on ARCH_SUNXI
>> +	depends on HWSPINLOCK
>> +	help
>> +	  Say y here to support the SUNXI Hardware Spinlock device.
>> +
>> +	  If unsure, say N.
> 
> sunxi doesn't really mean anything though, the A10 is also part of the
> sunxi family and doesn't have that IP. Similarly, nothing prevents a
> future SoC from changing that design. The first SoC that used it was
> the A33 iirc, so let's just use sun8i.

Fair enough, I will use the same for the symbols as well. TBH the
nomenclature is quite confusing in allwinner land...

Actually since this driver will initially support A64 shouldn't the
symbols really be prefixed with sun50i, since looking at
https://linux-sunxi.org/Allwinner_SoC_Family sun8i pertains to 32 bit A7
cores?

> 
<snip>
>> +
>> +	/*
>> +	 * make sure the module is enabled and clocked before reading
>> +	 * the module SYSSTATUS register
>> +	 */
> 
> You don't define that register anywhere?
> 
>> +	clk = devm_clk_get(&pdev->dev, NULL);
>> +	if (IS_ERR(clk))
>> +		return PTR_ERR(clk);
>> +
>> +	ret = clk_prepare_enable(clk);
>> +	if (ret) {
>> +		dev_err(&pdev->dev, "Cannot enable clock\n");
>> +		return ret;
>> +	}
> 
> Can't we do that with runtime_pm?

Probably can but I'm new to device driver development so I don't
understand all the implications of using the pm framework. I saw that
other drivers did this but atm it's terra incognita to me.

> 
>> +	/* Disable soft reset */
>> +        reset= devm_reset_control_get_exclusive(&pdev->dev, NULL);
>> +        if (IS_ERR(reset)) {
>> +                ret = PTR_ERR(reset);
>> +                goto out_declock;
>> +        }
>> +        reset_control_deassert(reset);
> 
> We might have the same issue than the mailbox driver where the
> firmware will need to access the block at any time, so we can't really
> toggle the reset line as we want.

What should we do then ?

> 
>> +	num_locks = sunxi_get_num_locks(io_base);
>> +	if (!num_locks) {
>> +		dev_err(&pdev->dev, "Unrecognised sunxi hwspinlock device\n");
>> +		ret = -EINVAL;
>> +		goto out_reset;
>> +	}
>> +
>> +	hw = devm_kzalloc(&pdev->dev, sizeof(*hw) +
>> +			  num_locks * sizeof(struct hwspinlock), GFP_KERNEL);
>> +	if (!hw) {
>> +		ret = -ENOMEM;
>> +		goto out_reset;
>> +	}
> 
> That looks rather convoluted (especially since the variable length
> array is at the second level), and can be made more obvious by:
> 
> - Removing the hwspinlock_device from sunxi_hwspinlock and allocating
>   both separately.
> 
> - And then allocate the hwspinlock_device separately with struct_size

Actually this can be allocated via struct_size e.g. struct_size(hw,
bank.lock, num_locks).

> 
>> +	hw->clk = clk;
>> +	hw->reset = reset;
> 
> Why not using the structure directly instead of having temporary
> variables?

Because I use the clk/reset before allocating the devmem.

> 
>> +	io_base += LOCK_BASE_OFFSET;
>> +	for (i = 0; i < num_locks; i++)
>> +		hw->bank.lock[i].priv = io_base + i * sizeof(u32);
> 
> Using a define for the registers offset would be nice here. 

you mean something like:

# define LOCK(X) (x * sizeof(u32))

I think this brings more needless indirection than helps but if you
insist I won't mind.

> 
>> +	platform_set_drvdata(pdev, hw);
>> +
>> +	ret = hwspin_lock_register(&hw->bank, &pdev->dev, &sunxi_hwspinlock_ops,
>> +				   0, num_locks);
>> +
>> +	if (!ret)
>> +		return ret;
> 
> That's a slightly weird construct too, since in pretty much all the
> driver you return early on error and here you return early on
> success. Just return ret if there's an error just like you're doing
> everywhere else, and return 0 after that.
> 
>> +out_reset:
>> +	reset_control_assert(reset);
>> +out_declock:
>> +	clk_disable_unprepare(clk);
>> +	return ret;
>> +}
>> +

<snip>

>> +/* board init code might need to reserve hwspinlocks for predefined purposes */
>> +postcore_initcall(sunxi_hwspinlock_init);
> 
> We don't have any board init code. Can't we just use a regular
> module_platform_driver call here?

Perhaps we can, I will test on my pine64.
> 
> Thanks!
> Maxime
>
Maxime Ripard Feb. 11, 2020, 12:34 p.m. UTC | #6
On Tue, Feb 11, 2020 at 10:08:08AM +0200, Nikolay Borisov wrote:
> On 11.02.20 г. 9:46 ч., Maxime Ripard wrote:
> > Hi,
> >
> > On Mon, Feb 10, 2020 at 07:01:41PM +0200, Nikolay Borisov wrote:
> >> Based on the datasheet this implements support for the hwspinlock IP
> >> block.
> >
> > How was this tested?
>
> I tested it on my pine64 lts e.g. loading the driver and reading the
> reset/clock/sysstatus registers to ensure everything is unmasked and has
> expected values.

Isn't the point of hwspinlocks that it's shared between the ARISC core
and the ARM cores. How did you test that the lock was actually taken
on the other side just by using the ARM cores?

> >
> > There's also a lot of checkpatch issues, make sure you fix those.
> >
> >> Signed-off-by: Nikolay Borisov <nborisov@suse.com>
> >> ---
> >>  drivers/hwspinlock/Kconfig            |   9 ++
> >>  drivers/hwspinlock/Makefile           |   1 +
> >>  drivers/hwspinlock/sunxi_hwspinlock.c | 181 ++++++++++++++++++++++++++
> >>  3 files changed, 191 insertions(+)
> >>  create mode 100644 drivers/hwspinlock/sunxi_hwspinlock.c
> >>
> >> diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig
> >> index 37740e992cfa..ebc1ea48ef16 100644
> >> --- a/drivers/hwspinlock/Kconfig
> >> +++ b/drivers/hwspinlock/Kconfig
> >> @@ -68,3 +68,12 @@ config HSEM_U8500
> >>  	  SoC.
> >>
> >>  	  If unsure, say N.
> >> +
> >> +config HWSPINLOCK_SUNXI
> >> +	tristate "Allwinner Hardware Spinlock device"
> >> +	depends on ARCH_SUNXI
> >> +	depends on HWSPINLOCK
> >> +	help
> >> +	  Say y here to support the SUNXI Hardware Spinlock device.
> >> +
> >> +	  If unsure, say N.
> >
> > sunxi doesn't really mean anything though, the A10 is also part of the
> > sunxi family and doesn't have that IP. Similarly, nothing prevents a
> > future SoC from changing that design. The first SoC that used it was
> > the A33 iirc, so let's just use sun8i.
>
> Fair enough, I will use the same for the symbols as well. TBH the
> nomenclature is quite confusing in allwinner land...
>
> Actually since this driver will initially support A64 shouldn't the
> symbols really be prefixed with sun50i, since looking at
> https://linux-sunxi.org/Allwinner_SoC_Family sun8i pertains to 32 bit A7
> cores?

Not really, the design is the same and we don't want to rename
everything.

> >
> <snip>
> >> +
> >> +	/*
> >> +	 * make sure the module is enabled and clocked before reading
> >> +	 * the module SYSSTATUS register
> >> +	 */
> >
> > You don't define that register anywhere?
> >
> >> +	clk = devm_clk_get(&pdev->dev, NULL);
> >> +	if (IS_ERR(clk))
> >> +		return PTR_ERR(clk);
> >> +
> >> +	ret = clk_prepare_enable(clk);
> >> +	if (ret) {
> >> +		dev_err(&pdev->dev, "Cannot enable clock\n");
> >> +		return ret;
> >> +	}
> >
> > Can't we do that with runtime_pm?
>
> Probably can but I'm new to device driver development so I don't
> understand all the implications of using the pm framework. I saw that
> other drivers did this but atm it's terra incognita to me.
>
> >
> >> +	/* Disable soft reset */
> >> +        reset= devm_reset_control_get_exclusive(&pdev->dev, NULL);
> >> +        if (IS_ERR(reset)) {
> >> +                ret = PTR_ERR(reset);
> >> +                goto out_declock;
> >> +        }
> >> +        reset_control_deassert(reset);
> >
> > We might have the same issue than the mailbox driver where the
> > firmware will need to access the block at any time, so we can't really
> > toggle the reset line as we want.
>
> What should we do then ?

Unless you put a firmware on the ARISC core and see how it interacts
between the two, you can't really do anything.

> >> +	num_locks = sunxi_get_num_locks(io_base);
> >> +	if (!num_locks) {
> >> +		dev_err(&pdev->dev, "Unrecognised sunxi hwspinlock device\n");
> >> +		ret = -EINVAL;
> >> +		goto out_reset;
> >> +	}
> >> +
> >> +	hw = devm_kzalloc(&pdev->dev, sizeof(*hw) +
> >> +			  num_locks * sizeof(struct hwspinlock), GFP_KERNEL);
> >> +	if (!hw) {
> >> +		ret = -ENOMEM;
> >> +		goto out_reset;
> >> +	}
> >
> > That looks rather convoluted (especially since the variable length
> > array is at the second level), and can be made more obvious by:
> >
> > - Removing the hwspinlock_device from sunxi_hwspinlock and allocating
> >   both separately.
> >
> > - And then allocate the hwspinlock_device separately with struct_size
>
> Actually this can be allocated via struct_size e.g. struct_size(hw,
> bank.lock, num_locks).
>
> >
> >> +	hw->clk = clk;
> >> +	hw->reset = reset;
> >
> > Why not using the structure directly instead of having temporary
> > variables?
>
> Because I use the clk/reset before allocating the devmem.

Then do it the other way around?

> >
> >> +	io_base += LOCK_BASE_OFFSET;
> >> +	for (i = 0; i < num_locks; i++)
> >> +		hw->bank.lock[i].priv = io_base + i * sizeof(u32);
> >
> > Using a define for the registers offset would be nice here.
>
> you mean something like:
>
> # define LOCK(X) (x * sizeof(u32))
>
> I think this brings more needless indirection than helps but if you
> insist I won't mind.

It's not more of an indirection than using a function, and you're
doing that all the time already :)

Maxime
Nikolay Borisov Feb. 11, 2020, 1:17 p.m. UTC | #7
On 11.02.20 г. 14:34 ч., Maxime Ripard wrote:
> On Tue, Feb 11, 2020 at 10:08:08AM +0200, Nikolay Borisov wrote:
>> On 11.02.20 г. 9:46 ч., Maxime Ripard wrote:
>>> Hi,
>>>
>>> On Mon, Feb 10, 2020 at 07:01:41PM +0200, Nikolay Borisov wrote:
>>>> Based on the datasheet this implements support for the hwspinlock IP
>>>> block.
>>>
>>> How was this tested?
>>
>> I tested it on my pine64 lts e.g. loading the driver and reading the
>> reset/clock/sysstatus registers to ensure everything is unmasked and has
>> expected values.
> 
> Isn't the point of hwspinlocks that it's shared between the ARISC core
> and the ARM cores. How did you test that the lock was actually taken
> on the other side just by using the ARM cores?

I haven't. I'm really focuse don just enabling this on the linux side of
things. True, hw spinlocks are used to synchronize cpu running different
OS'es. It's still implementation defined which hwspinlock is used for
which component. Additionally if we assume the ARISC core uses spinlock
this means by the time linux is booted the spinlocks should already be
clocked and out of software reset so perhahps this is also redundant in
the driver?


<snip>
Maxime Ripard Feb. 12, 2020, 12:06 p.m. UTC | #8
On Tue, Feb 11, 2020 at 03:17:40PM +0200, Nikolay Borisov wrote:
>
>
> On 11.02.20 г. 14:34 ч., Maxime Ripard wrote:
> > On Tue, Feb 11, 2020 at 10:08:08AM +0200, Nikolay Borisov wrote:
> >> On 11.02.20 г. 9:46 ч., Maxime Ripard wrote:
> >>> Hi,
> >>>
> >>> On Mon, Feb 10, 2020 at 07:01:41PM +0200, Nikolay Borisov wrote:
> >>>> Based on the datasheet this implements support for the hwspinlock IP
> >>>> block.
> >>>
> >>> How was this tested?
> >>
> >> I tested it on my pine64 lts e.g. loading the driver and reading the
> >> reset/clock/sysstatus registers to ensure everything is unmasked and has
> >> expected values.
> >
> > Isn't the point of hwspinlocks that it's shared between the ARISC core
> > and the ARM cores. How did you test that the lock was actually taken
> > on the other side just by using the ARM cores?
>
> I haven't. I'm really focuse don just enabling this on the linux side of
> things. True, hw spinlocks are used to synchronize cpu running different
> OS'es.

I'm sorry but this driver hasn't been really tested then. The whole
point of it is to synchronise with something. If you tested without
that something, it's just like testing a network driver without having
anything connected on the network you're testing it on: it probably
looks like it's working, but you really can't tell.

> It's still implementation defined which hwspinlock is used for
> which component. Additionally if we assume the ARISC core uses spinlock
> this means by the time linux is booted the spinlocks should already be
> clocked and out of software reset so perhahps this is also redundant in
> the driver?

Linux also likes to disable the clocks no one is using, so in such a
situation, what would happen? Can the ARISC still use them, should we
maintain the enabled all the time?

This is exactly the kind of corner-cases that we need a test for.

Maxime
Nikolay Borisov Feb. 12, 2020, 2:32 p.m. UTC | #9
On 12.02.20 г. 14:06 ч., Maxime Ripard wrote:
> On Tue, Feb 11, 2020 at 03:17:40PM +0200, Nikolay Borisov wrote:
>>
>>
>> On 11.02.20 г. 14:34 ч., Maxime Ripard wrote:
>>> On Tue, Feb 11, 2020 at 10:08:08AM +0200, Nikolay Borisov wrote:
>>>> On 11.02.20 г. 9:46 ч., Maxime Ripard wrote:
>>>>> Hi,
>>>>>
>>>>> On Mon, Feb 10, 2020 at 07:01:41PM +0200, Nikolay Borisov wrote:
>>>>>> Based on the datasheet this implements support for the hwspinlock IP
>>>>>> block.
>>>>>
>>>>> How was this tested?
>>>>
>>>> I tested it on my pine64 lts e.g. loading the driver and reading the
>>>> reset/clock/sysstatus registers to ensure everything is unmasked and has
>>>> expected values.
>>>
>>> Isn't the point of hwspinlocks that it's shared between the ARISC core
>>> and the ARM cores. How did you test that the lock was actually taken
>>> on the other side just by using the ARM cores?
>>
>> I haven't. I'm really focuse don just enabling this on the linux side of
>> things. True, hw spinlocks are used to synchronize cpu running different
>> OS'es.
> 
> I'm sorry but this driver hasn't been really tested then. The whole
> point of it is to synchronise with something. If you tested without

I disagree, the whole point is to expose the facility for other drivers
which, in turn, might need to synchronize with that other thing. I see
the hwspinlock driver as a dumb provider of the interface. The only
pertinent contention point I see is how should the clock/soft reset
registers be programmed considering the spinlocks might be accessed
outside of linux.

> that something, it's just like testing a network driver without having
> anything connected on the network you're testing it on: it probably
> looks like it's working, but you really can't tell.
> 
>> It's still implementation defined which hwspinlock is used for
>> which component. Additionally if we assume the ARISC core uses spinlock
>> this means by the time linux is booted the spinlocks should already be
>> clocked and out of software reset so perhahps this is also redundant in
>> the driver?
> 
> Linux also likes to disable the clocks no one is using, so in such a
> situation, what would happen? Can the ARISC still use them, should we
> maintain the enabled all the time?
> 
> This is exactly the kind of corner-cases that we need a test for.


Fair point BUT, and this is one big BUT. This other thing (the ARISC fw)
doesn't have a contract with the kernel on synchronization. So should
the test be performed against allwinner's binary blob or against some of
the open source alternatives ( I only saw it mentioned on the sunxi web
page but no links to such open source alternatives).


Furthermore, if we assume we should be compatible with the binary blob
it needs to be RE'd in order to understand which hw spinlocks it's using
for synchronization and I'm not aware of any such effort.

> 
> Maxime
>
Maxime Ripard Feb. 12, 2020, 7:10 p.m. UTC | #10
On Wed, Feb 12, 2020 at 04:32:11PM +0200, Nikolay Borisov wrote:
>
>
> On 12.02.20 г. 14:06 ч., Maxime Ripard wrote:
> > On Tue, Feb 11, 2020 at 03:17:40PM +0200, Nikolay Borisov wrote:
> >>
> >>
> >> On 11.02.20 г. 14:34 ч., Maxime Ripard wrote:
> >>> On Tue, Feb 11, 2020 at 10:08:08AM +0200, Nikolay Borisov wrote:
> >>>> On 11.02.20 г. 9:46 ч., Maxime Ripard wrote:
> >>>>> Hi,
> >>>>>
> >>>>> On Mon, Feb 10, 2020 at 07:01:41PM +0200, Nikolay Borisov wrote:
> >>>>>> Based on the datasheet this implements support for the hwspinlock IP
> >>>>>> block.
> >>>>>
> >>>>> How was this tested?
> >>>>
> >>>> I tested it on my pine64 lts e.g. loading the driver and reading the
> >>>> reset/clock/sysstatus registers to ensure everything is unmasked and has
> >>>> expected values.
> >>>
> >>> Isn't the point of hwspinlocks that it's shared between the ARISC core
> >>> and the ARM cores. How did you test that the lock was actually taken
> >>> on the other side just by using the ARM cores?
> >>
> >> I haven't. I'm really focuse don just enabling this on the linux side of
> >> things. True, hw spinlocks are used to synchronize cpu running different
> >> OS'es.
> >
> > I'm sorry but this driver hasn't been really tested then. The whole
> > point of it is to synchronise with something. If you tested without
>
> I disagree, the whole point is to expose the facility for other drivers
> which, in turn, might need to synchronize with that other thing.

And you haven't tested that either.

> I see the hwspinlock driver as a dumb provider of the interface. The
> only pertinent contention point I see is how should the clock/soft
> reset registers be programmed considering the spinlocks might be
> accessed outside of linux.
>
> > that something, it's just like testing a network driver without having
> > anything connected on the network you're testing it on: it probably
> > looks like it's working, but you really can't tell.
> >
> >> It's still implementation defined which hwspinlock is used for
> >> which component. Additionally if we assume the ARISC core uses spinlock
> >> this means by the time linux is booted the spinlocks should already be
> >> clocked and out of software reset so perhahps this is also redundant in
> >> the driver?
> >
> > Linux also likes to disable the clocks no one is using, so in such a
> > situation, what would happen? Can the ARISC still use them, should we
> > maintain the enabled all the time?
> >
> > This is exactly the kind of corner-cases that we need a test for.
>
> Fair point BUT, and this is one big BUT. This other thing (the ARISC fw)
> doesn't have a contract with the kernel on synchronization.

You really need two things here:

 - A hwspinlock driver that works, and you can see that on the other
   side you have that lock reported as taken (either taking one on the
   ARISC and seeing that it's taken on the ARM core, or the other way
   around). You don't really need a contract for that to do that kind
   of test, you can hack your own.

 - Once that driver is working, indeed, we'll need to come up with
   that contract. It's not that hard, really, it's just assigning a
   number to every device that could be shared, and we're even in a
   position where we can probably force that, and every implementation
   would have to comply. But that's a second step.

> So should the test be performed against allwinner's binary blob or
> against some of the open source alternatives ( I only saw it
> mentioned on the sunxi web page but no links to such open source
> alternatives).
>
> Furthermore, if we assume we should be compatible with the binary blob
> it needs to be RE'd in order to understand which hw spinlocks it's using
> for synchronization and I'm not aware of any such effort.

You don't really need to care about the Allwinner firmware. If they
already have a list of devices they have assigned, it's just as good
as any and if it makes sense we can use it, but if they don't or if it
doesn't make sense, I'm totally fine ignoring that driver as well.

The best thing you should target right now is crust:
https://github.com/crust-firmware/crust

Maxime
diff mbox series

Patch

diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig
index 37740e992cfa..ebc1ea48ef16 100644
--- a/drivers/hwspinlock/Kconfig
+++ b/drivers/hwspinlock/Kconfig
@@ -68,3 +68,12 @@  config HSEM_U8500
 	  SoC.
 
 	  If unsure, say N.
+
+config HWSPINLOCK_SUNXI
+	tristate "Allwinner Hardware Spinlock device"
+	depends on ARCH_SUNXI
+	depends on HWSPINLOCK
+	help
+	  Say y here to support the SUNXI Hardware Spinlock device.
+
+	  If unsure, say N.
diff --git a/drivers/hwspinlock/Makefile b/drivers/hwspinlock/Makefile
index ed053e3f02be..6be5ebef906e 100644
--- a/drivers/hwspinlock/Makefile
+++ b/drivers/hwspinlock/Makefile
@@ -8,5 +8,6 @@  obj-$(CONFIG_HWSPINLOCK_OMAP)		+= omap_hwspinlock.o
 obj-$(CONFIG_HWSPINLOCK_QCOM)		+= qcom_hwspinlock.o
 obj-$(CONFIG_HWSPINLOCK_SIRF)		+= sirf_hwspinlock.o
 obj-$(CONFIG_HWSPINLOCK_SPRD)		+= sprd_hwspinlock.o
+obj-$(CONFIG_HWSPINLOCK_SUNXI)		+= sunxi_hwspinlock.o
 obj-$(CONFIG_HWSPINLOCK_STM32)		+= stm32_hwspinlock.o
 obj-$(CONFIG_HSEM_U8500)		+= u8500_hsem.o
diff --git a/drivers/hwspinlock/sunxi_hwspinlock.c b/drivers/hwspinlock/sunxi_hwspinlock.c
new file mode 100644
index 000000000000..8e5685357fbf
--- /dev/null
+++ b/drivers/hwspinlock/sunxi_hwspinlock.c
@@ -0,0 +1,181 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Author: Nikolay Borisov <nborisov@suse.com> */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/hwspinlock.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include "hwspinlock_internal.h"
+
+/* Spinlock register offsets */
+#define LOCK_BASE_OFFSET		0x0100
+
+#define SPINLOCK_NUMLOCKS_BIT_OFFSET	(28)
+/* Possible values of SPINLOCK_LOCK_REG */
+#define SPINLOCK_NOTTAKEN		(0)	/* free */
+#define SPINLOCK_TAKEN			(1)	/* locked */
+
+struct sunxi_hwspinlock {
+	struct clk *clk;
+	struct reset_control *reset;
+	struct hwspinlock_device bank;
+};
+
+static int sunxi_hwspinlock_trylock(struct hwspinlock *lock)
+{
+	void __iomem *lock_addr = lock->priv;
+
+	/* attempt to acquire the lock by reading its value */
+	return (SPINLOCK_NOTTAKEN == readl(lock_addr));
+}
+
+static void sunxi_hwspinlock_unlock(struct hwspinlock *lock)
+{
+	void __iomem *lock_addr = lock->priv;
+
+	/* release the lock by writing 0 to it */
+	writel(SPINLOCK_NOTTAKEN, lock_addr);
+}
+
+static const struct hwspinlock_ops sunxi_hwspinlock_ops = {
+	.trylock	= sunxi_hwspinlock_trylock,
+	.unlock		= sunxi_hwspinlock_unlock,
+};
+
+static int sunxi_get_num_locks(void __iomem *io_base)
+{
+	int i = readl(io_base);
+	i >>= SPINLOCK_NUMLOCKS_BIT_OFFSET;
+
+	switch (i) {
+	case 0x1: return 32;
+	case 0x2: return 64;
+	case 0x3: return 128;
+	case 0x4: return 256;
+	}
+
+	return 0;
+}
+
+static int sunxi_hwspinlock_probe(struct platform_device *pdev)
+{
+	struct sunxi_hwspinlock *hw;
+	void __iomem *io_base;
+	struct resource *res;
+	struct clk *clk;
+	struct reset_control *reset;
+	int i, ret, num_locks;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	io_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(io_base))
+		return PTR_ERR(io_base);
+
+	/*
+	 * make sure the module is enabled and clocked before reading
+	 * the module SYSSTATUS register
+	 */
+	clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	ret = clk_prepare_enable(clk);
+	if (ret) {
+		dev_err(&pdev->dev, "Cannot enable clock\n");
+		return ret;
+	}
+
+	/* Disable soft reset */
+        reset= devm_reset_control_get_exclusive(&pdev->dev, NULL);
+        if (IS_ERR(reset)) {
+                ret = PTR_ERR(reset);
+                goto out_declock;
+        }
+        reset_control_deassert(reset);
+
+	num_locks = sunxi_get_num_locks(io_base);
+	if (!num_locks) {
+		dev_err(&pdev->dev, "Unrecognised sunxi hwspinlock device\n");
+		ret = -EINVAL;
+		goto out_reset;
+	}
+
+	hw = devm_kzalloc(&pdev->dev, sizeof(*hw) +
+			  num_locks * sizeof(struct hwspinlock), GFP_KERNEL);
+	if (!hw) {
+		ret = -ENOMEM;
+		goto out_reset;
+	}
+
+	hw->clk = clk;
+	hw->reset = reset;
+
+	io_base += LOCK_BASE_OFFSET;
+	for (i = 0; i < num_locks; i++)
+		hw->bank.lock[i].priv = io_base + i * sizeof(u32);
+
+	platform_set_drvdata(pdev, hw);
+
+	ret = hwspin_lock_register(&hw->bank, &pdev->dev, &sunxi_hwspinlock_ops,
+				   0, num_locks);
+
+	if (!ret)
+		return ret;
+out_reset:
+	reset_control_assert(reset);
+out_declock:
+	clk_disable_unprepare(clk);
+	return ret;
+}
+
+static int sunxi_hwspinlock_remove(struct platform_device *pdev)
+{
+	struct sunxi_hwspinlock *hw = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = hwspin_lock_unregister(&hw->bank);
+	if (ret)
+		dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret);
+
+	reset_control_assert(hw->reset);
+	clk_disable_unprepare(hw->clk);
+
+	return 0;
+}
+
+static const struct of_device_id sunxi_hwpinlock_ids[] = {
+	{ .compatible = "allwinner,sun50i-a64-hwspinlock", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sunxi_hwpinlock_ids);
+
+static struct platform_driver sunxi_hwspinlock_driver = {
+	.probe		= sunxi_hwspinlock_probe,
+	.remove		= sunxi_hwspinlock_remove,
+	.driver		= {
+		.name	= "sunxi_hwspinlock",
+		.of_match_table = sunxi_hwpinlock_ids,
+	},
+};
+
+static int __init sunxi_hwspinlock_init(void)
+{
+	return platform_driver_register(&sunxi_hwspinlock_driver);
+}
+/* board init code might need to reserve hwspinlocks for predefined purposes */
+postcore_initcall(sunxi_hwspinlock_init);
+
+static void __exit sunxi_hwspinlock_exit(void)
+{
+	platform_driver_unregister(&sunxi_hwspinlock_driver);
+}
+module_exit(sunxi_hwspinlock_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Hardware spinlock driver for sunxi SoCs");
+MODULE_AUTHOR("Nikolay Borisov <nborisov@suse.com>");