diff mbox series

[2/2] bootcount: Add driver model I2C driver

Message ID 20231013094302.28944-3-pro@denx.de
State Superseded
Delegated to: Heiko Schocher
Headers show
Series bootcount: Replace I2C legacy implementation by driver model | expand

Commit Message

Philip Oberfichtner Oct. 13, 2023, 9:43 a.m. UTC
This adds a generic I2C bootcounter adhering to driver model to replace
the previously removed legacy implementation.

There is no change in functionality, it can be used on any I2C device.
The device tree configuration may look like this for example:

	bootcount {
		compatible = "u-boot,bootcount-i2c";
		i2c-bus = <&i2c1>;
		address = <0x52>;
		offset = <0x11>;
	};

Signed-off-by: Philip Richard Oberfichtner <pro@denx.de>
---
 drivers/bootcount/Kconfig            |  10 +++
 drivers/bootcount/Makefile           |   1 +
 drivers/bootcount/bootcount_dm_i2c.c | 126 +++++++++++++++++++++++++++
 3 files changed, 137 insertions(+)
 create mode 100644 drivers/bootcount/bootcount_dm_i2c.c

Comments

Heiko Schocher Oct. 13, 2023, 11:28 a.m. UTC | #1
Hello Philip,

On 13.10.23 11:43, Philip Richard Oberfichtner wrote:
> This adds a generic I2C bootcounter adhering to driver model to replace
> the previously removed legacy implementation.
> 
> There is no change in functionality, it can be used on any I2C device.
> The device tree configuration may look like this for example:
> 
> 	bootcount {
> 		compatible = "u-boot,bootcount-i2c";
> 		i2c-bus = <&i2c1>;
> 		address = <0x52>;

Hmm.. do we really need this here with DTS. Why not using a phandle
to a real i2c device? Something like this for example:

		i2cbcdev = &i2c_rtc;

with

&i2c1 {
        i2c_rtc: rtc@68 {
[...]

and so there is no need for knowing the bus and address ...

> 		offset = <0x11>;
> 	};
> 
> Signed-off-by: Philip Richard Oberfichtner <pro@denx.de>
> ---
>  drivers/bootcount/Kconfig            |  10 +++
>  drivers/bootcount/Makefile           |   1 +
>  drivers/bootcount/bootcount_dm_i2c.c | 126 +++++++++++++++++++++++++++
>  3 files changed, 137 insertions(+)
>  create mode 100644 drivers/bootcount/bootcount_dm_i2c.c
> 
> diff --git a/drivers/bootcount/Kconfig b/drivers/bootcount/Kconfig
> index 7a2548ace2..1cf1de2b6d 100644
> --- a/drivers/bootcount/Kconfig
> +++ b/drivers/bootcount/Kconfig
> @@ -109,6 +109,16 @@ config DM_BOOTCOUNT_RTC
>  	  Accesses to the backing store are performed using the write16
>  	  and read16 ops of DM RTC devices.
>  
> +config DM_BOOTCOUNT_I2C
> +	bool "Driver Model boot counter on I2C device"
> +	depends on DM_I2C
> +	help
> +	  Enable support for the bootcounter on a generic i2c device, like a
> +	  RTC or PMIC. This requires an 'i2c-bus', the i2c chip 'address' and
> +	  the 'offset' to read to and write from. All of the three settings are
> +	  defined as device tree properties using the "u-boot,bootcount-i2c"
> +	  compatible string.
> +
>  config DM_BOOTCOUNT_I2C_EEPROM
>  	bool "Support i2c eeprom devices as a backing store for bootcount"
>  	depends on I2C_EEPROM
> diff --git a/drivers/bootcount/Makefile b/drivers/bootcount/Makefile
> index d6d2389c16..e7771f5b36 100644
> --- a/drivers/bootcount/Makefile
> +++ b/drivers/bootcount/Makefile
> @@ -13,5 +13,6 @@ obj-$(CONFIG_DM_BOOTCOUNT)      += bootcount-uclass.o
>  obj-$(CONFIG_DM_BOOTCOUNT_PMIC_PFUZE100) += pmic_pfuze100.o
>  obj-$(CONFIG_DM_BOOTCOUNT_RTC)  += rtc.o
>  obj-$(CONFIG_DM_BOOTCOUNT_I2C_EEPROM)	+= i2c-eeprom.o
> +obj-$(CONFIG_DM_BOOTCOUNT_I2C)	+= bootcount_dm_i2c.o
>  obj-$(CONFIG_DM_BOOTCOUNT_SPI_FLASH)	+= spi-flash.o
>  obj-$(CONFIG_DM_BOOTCOUNT_SYSCON) += bootcount_syscon.o
> diff --git a/drivers/bootcount/bootcount_dm_i2c.c b/drivers/bootcount/bootcount_dm_i2c.c
> new file mode 100644
> index 0000000000..227641f77e
> --- /dev/null
> +++ b/drivers/bootcount/bootcount_dm_i2c.c
> @@ -0,0 +1,126 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * (C) Copyright 2023
> + * Philip Richard Oberfichtner <pro@denx.de>
> + *
> + * Based on previous work from Heiko Schocher (legacy bootcount_i2c.c driver)
> + */
> +
> +#include <bootcount.h>
> +#include <common.h>
> +#include <dm.h>
> +#include <i2c.h>
> +
> +#define BC_MAGIC	0x55
> +
> +struct bootcount_i2c_priv {
> +	struct udevice *chip;

may rename chip to i2cbcdev or bcdev ?

> +	unsigned int offset;
> +};
> +
> +static int bootcount_i2c_set(struct udevice *dev, const u32 val)
> +{
> +	int ret;
> +	struct bootcount_i2c_priv *priv = dev_get_priv(dev);
> +
> +	ret = dm_i2c_reg_write(priv->chip, priv->offset, BC_MAGIC);
> +	if (ret < 0)
> +		goto err_exit;
> +
> +	ret = dm_i2c_reg_write(priv->chip, priv->offset + 1, val & 0xff);
> +	if (ret < 0)
> +		goto err_exit;
> +
> +	return 0;
> +
> +err_exit:
> +	log_debug("%s: Error writing to I2C device (%d)\n", __func__, ret);
> +	return ret;
> +}
> +
> +static int bootcount_i2c_get(struct udevice *dev, u32 *val)
> +{
> +	int ret;
> +	struct bootcount_i2c_priv *priv = dev_get_priv(dev);
> +
> +	ret = dm_i2c_reg_read(priv->chip, priv->offset);
> +	if (ret < 0)
> +		goto err_exit;
> +
> +	if ((ret & 0xff) != BC_MAGIC) {
> +		log_debug("%s: Invalid Magic, reset bootcounter.\n", __func__);
> +		*val = 0;
> +		return bootcount_i2c_set(dev, 0);
> +	}
> +
> +	ret = dm_i2c_reg_read(priv->chip, priv->offset + 1);
> +	if (ret < 0)
> +		goto err_exit;
> +
> +	*val = ret;
> +	return 0;
> +
> +err_exit:
> +	log_debug("%s: Error reading from I2C device (%d)\n", __func__, ret);
> +	return ret;
> +}
> +
> +static int bootcount_i2c_probe(struct udevice *dev)
> +{
> +	struct bootcount_i2c_priv *priv = dev_get_priv(dev);
> +	struct ofnode_phandle_args phandle_args;
> +	struct udevice *bus;
> +	unsigned int addr;
> +	int ret;
> +
> +	ret = dev_read_u32(dev, "offset", &priv->offset);
> +	if (ret) {
> +		log_debug("%s: Unable to get offset\n", __func__);
> +		return ret;
> +	}
> +
> +	ret = dev_read_u32(dev, "address", &addr);
> +	if (ret) {
> +		log_debug("%s: Unable to get chip address\n", __func__);
> +		return ret;
> +	}
> +
> +	ret = dev_read_phandle_with_args(dev, "i2c-bus", NULL, 0, 0, &phandle_args);
> +	if (ret) {
> +		log_debug("%s: Unable to get phandle\n", __func__);
> +		return ret;
> +	}
> +
> +	ret = uclass_get_device_by_ofnode(UCLASS_I2C, phandle_args.node, &bus);
> +	if (ret) {
> +		log_debug("%s: Unable to get i2c bus\n", __func__);
> +		return ret;
> +	}
> +
> +	ret = i2c_get_chip(bus, addr, 1, &priv->chip);
> +	if (ret) {
> +		log_debug("%s: Unable to get i2c chip\n", __func__);
> +		return ret;
> +	}

when you use a phandle, you can replace the part from reading "offset"
with this:

	uclass_get_device_by_phandle(UCLASS_I2C, dev, "i2cbcdev", &priv->bcdev);

of course plus error checking...

> +
> +	return 0;
> +}
> +
> +static const struct bootcount_ops bootcount_i2c_ops = {
> +	.get = bootcount_i2c_get,
> +	.set = bootcount_i2c_set,
> +};
> +
> +static const struct udevice_id bootcount_i2c_ids[] = {
> +	{ .compatible = "u-boot,bootcount-i2c" },
> +	{ }
> +};
> +
> +U_BOOT_DRIVER(bootcount_i2c) = {
> +	.name		= "bootcount-i2c",
> +	.id		= UCLASS_BOOTCOUNT,
> +	.priv_auto	= sizeof(struct bootcount_i2c_priv),
> +	.probe		= bootcount_i2c_probe,
> +	.of_match	= bootcount_i2c_ids,
> +	.ops		= &bootcount_i2c_ops,
> +};
> 

bye,
Heiko
Philip Oberfichtner Oct. 13, 2023, 12:58 p.m. UTC | #2
Hello Heiko,

On Fri, Oct 13, 2023 at 01:28:47PM +0200, Heiko Schocher wrote:
> [...]
> > 
> > 	bootcount {
> > 		compatible = "u-boot,bootcount-i2c";
> > 		i2c-bus = <&i2c1>;
> > 		address = <0x52>;
> 
> Hmm.. do we really need this here with DTS. Why not using a phandle
> to a real i2c device? Something like this for example:
> 
> 		i2cbcdev = &i2c_rtc;
> 
> with
> 
> &i2c1 {
>         i2c_rtc: rtc@68 {
> [...]
> 
> and so there is no need for knowing the bus and address ...
> 

Yeah I agree that would be much better, but ...

> [...]
> 
> when you use a phandle, you can replace the part from reading "offset"
> with this:
> 
> 	uclass_get_device_by_phandle(UCLASS_I2C, dev, "i2cbcdev", &priv->bcdev);
> 
> of course plus error checking...

This does not work, UCLASS_I2C is used for the *bus above* the device we
actually want.

Next thing I thougt about: Why not use the UCLASS_I2C_GENERIC. But this
expects a "i2c-chip" compatible string, which our rtc or whatever device
obviously does not have.

I think using UCLASS_I2C_GENERIC might indeed be the best approach.
Maybe using something like 'device_bind_driver()'. But again, this
expects the parent device to be there already as far as I can tell.

Any suggestions?

Best regards,
Philip


> 
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct bootcount_ops bootcount_i2c_ops = {
> > +	.get = bootcount_i2c_get,
> > +	.set = bootcount_i2c_set,
> > +};
> > +
> > +static const struct udevice_id bootcount_i2c_ids[] = {
> > +	{ .compatible = "u-boot,bootcount-i2c" },
> > +	{ }
> > +};
> > +
> > +U_BOOT_DRIVER(bootcount_i2c) = {
> > +	.name		= "bootcount-i2c",
> > +	.id		= UCLASS_BOOTCOUNT,
> > +	.priv_auto	= sizeof(struct bootcount_i2c_priv),
> > +	.probe		= bootcount_i2c_probe,
> > +	.of_match	= bootcount_i2c_ids,
> > +	.ops		= &bootcount_i2c_ops,
> > +};
> > 
> 
> bye,
> Heiko
> -- 
> DENX Software Engineering GmbH,      Managing Director: Erika Unter
> HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
> Phone: +49-8142-66989-52   Fax: +49-8142-66989-80   Email: hs@denx.de
Philip Oberfichtner Oct. 17, 2023, 12:55 p.m. UTC | #3
Hi Simon,

maybe you can give me a hint on how to implement this cleanly in driver
model?

To sum it up, I'd like to have a phandle pointing to *any* I2C device,
not knowing which UCLASS actually fits. Then the device and the parent
bus should be probed/prepared such that dm_i2c_read() can be used.

Any ideas on this?

Best regards,
Philip


--
=====================================================================
DENX Software Engineering GmbH,        Managing Director: Erika Unter
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: +49-8142-66989-22  Fax: +49-8142-66989-80   Email: pro@denx.de
=====================================================================

On Fri, Oct 13, 2023 at 02:58:04PM +0200, Philip Oberfichtner wrote:
> Hello Heiko,
> 
> On Fri, Oct 13, 2023 at 01:28:47PM +0200, Heiko Schocher wrote:
> > [...]
> > > 
> > > 	bootcount {
> > > 		compatible = "u-boot,bootcount-i2c";
> > > 		i2c-bus = <&i2c1>;
> > > 		address = <0x52>;
> > 
> > Hmm.. do we really need this here with DTS. Why not using a phandle
> > to a real i2c device? Something like this for example:
> > 
> > 		i2cbcdev = &i2c_rtc;
> > 
> > with
> > 
> > &i2c1 {
> >         i2c_rtc: rtc@68 {
> > [...]
> > 
> > and so there is no need for knowing the bus and address ...
> > 
> 
> Yeah I agree that would be much better, but ...
> 
> > [...]
> > 
> > when you use a phandle, you can replace the part from reading "offset"
> > with this:
> > 
> > 	uclass_get_device_by_phandle(UCLASS_I2C, dev, "i2cbcdev", &priv->bcdev);
> > 
> > of course plus error checking...
> 
> This does not work, UCLASS_I2C is used for the *bus above* the device we
> actually want.
> 
> Next thing I thougt about: Why not use the UCLASS_I2C_GENERIC. But this
> expects a "i2c-chip" compatible string, which our rtc or whatever device
> obviously does not have.
> 
> I think using UCLASS_I2C_GENERIC might indeed be the best approach.
> Maybe using something like 'device_bind_driver()'. But again, this
> expects the parent device to be there already as far as I can tell.
> 
> Any suggestions?
> 
> Best regards,
> Philip
> 
> 
> > 
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static const struct bootcount_ops bootcount_i2c_ops = {
> > > +	.get = bootcount_i2c_get,
> > > +	.set = bootcount_i2c_set,
> > > +};
> > > +
> > > +static const struct udevice_id bootcount_i2c_ids[] = {
> > > +	{ .compatible = "u-boot,bootcount-i2c" },
> > > +	{ }
> > > +};
> > > +
> > > +U_BOOT_DRIVER(bootcount_i2c) = {
> > > +	.name		= "bootcount-i2c",
> > > +	.id		= UCLASS_BOOTCOUNT,
> > > +	.priv_auto	= sizeof(struct bootcount_i2c_priv),
> > > +	.probe		= bootcount_i2c_probe,
> > > +	.of_match	= bootcount_i2c_ids,
> > > +	.ops		= &bootcount_i2c_ops,
> > > +};
> > > 
> > 
> > bye,
> > Heiko
> > -- 
> > DENX Software Engineering GmbH,      Managing Director: Erika Unter
> > HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
> > Phone: +49-8142-66989-52   Fax: +49-8142-66989-80   Email: hs@denx.de
Simon Glass Oct. 18, 2023, 3:33 a.m. UTC | #4
Hi Philip,

On Tue, 17 Oct 2023 at 06:57, Philip Oberfichtner <pro@denx.de> wrote:
>
> Hi Simon,
>
> maybe you can give me a hint on how to implement this cleanly in driver
> model?
>
> To sum it up, I'd like to have a phandle pointing to *any* I2C device,
> not knowing which UCLASS actually fits. Then the device and the parent
> bus should be probed/prepared such that dm_i2c_read() can be used.
>
> Any ideas on this?

I suggest a phandle to the i2c device.

You can use oftree_get_by_phandle() to get the node and then
device_find_global_by_ofnode() to get the device.

This is expensive, although eventually I suspect we will fix that with
OF_LIVE. I think it should be implemented as a new function in i2c.h
so we can change the impl later easily.

If you want to be more efficient you could do something like:

int phandle = ??

struct uclass *uc;
struct udevice *bus;

uclass_id_foreach_dev(UCLASS_I2C, bus, uc) {
   struct udevice *dev;

   device_foreach_child(dev, bus) {
      if (!dev_read_u32(dev, "phandle", &val) && val == phandle)
         return dev;
   }
}

but honestly now I look at it, that is awful. We try to avoid exposing
the internals of phandle because it allows us to (one day) maintain a
list of them.

[..]

Regards,
Simon
Heiko Schocher Oct. 18, 2023, 4:31 a.m. UTC | #5
Hello Simon,

On 18.10.23 05:33, Simon Glass wrote:
> Hi Philip,
> 
> On Tue, 17 Oct 2023 at 06:57, Philip Oberfichtner <pro@denx.de> wrote:
>>
>> Hi Simon,
>>
>> maybe you can give me a hint on how to implement this cleanly in driver
>> model?
>>
>> To sum it up, I'd like to have a phandle pointing to *any* I2C device,
>> not knowing which UCLASS actually fits. Then the device and the parent
>> bus should be probed/prepared such that dm_i2c_read() can be used.
>>
>> Any ideas on this?
> 
> I suggest a phandle to the i2c device.

Yep!

> You can use oftree_get_by_phandle() to get the node and then
> device_find_global_by_ofnode() to get the device.
> 
> This is expensive, although eventually I suspect we will fix that with
> OF_LIVE. I think it should be implemented as a new function in i2c.h
> so we can change the impl later easily.

Yes, please.

> If you want to be more efficient you could do something like:
> 
> int phandle = ??
> 
> struct uclass *uc;
> struct udevice *bus;
> 
> uclass_id_foreach_dev(UCLASS_I2C, bus, uc) {
>    struct udevice *dev;
> 
>    device_foreach_child(dev, bus) {
>       if (!dev_read_u32(dev, "phandle", &val) && val == phandle)
>          return dev;
>    }
> }
> 
> but honestly now I look at it, that is awful. We try to avoid exposing
> the internals of phandle because it allows us to (one day) maintain a
> list of them.

Yes, please not ... a list of phandles would be great we can than
walk through, yes, may in future...

May Philip can use uclass_get_device_by_phandle and try a list of
possible UCLASS candidates, like UCLASS_RTC, UCLASS_I2C_EEPROM,
UCLASS_POWER,... and if found, check if parent is UCLASS_I2C...

may not so expensive ...

bye,
Heiko
Philip Oberfichtner Oct. 18, 2023, 10:58 a.m. UTC | #6
Hi Heiko,

On Wed, Oct 18, 2023 at 06:31:57AM +0200, Heiko Schocher wrote:
> [...]
> 
> May Philip can use uclass_get_device_by_phandle and try a list of
> possible UCLASS candidates, like UCLASS_RTC, UCLASS_I2C_EEPROM,
> UCLASS_POWER,... and if found, check if parent is UCLASS_I2C...
> 
> may not so expensive ...

Looks more cheap and elegant than the other variants indeed. But I'm not
sure if we can maintain generality using this approach. 

What if the specific I2C driver is not included in the build, because
the devices "actual" functionality is not required? Or what if a driver
for the specific device does not even exist in U-Boot?

Wouldn't the device fail to probe then?

Please correct me if I'm wrong, I'd give it a shot in that case.
Otherwise I'll try it with the aforementioned
device_find_global_by_ofnode() followed by device_bind_driver() using
UCLASS_I2C_GENERIC (I hope that works).

Best regards,
Philip


> 
> bye,
> Heiko
> -- 
> DENX Software Engineering GmbH,      Managing Director: Erika Unter
> HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
> Phone: +49-8142-66989-52   Fax: +49-8142-66989-80   Email: hs@denx.de
Simon Glass Oct. 18, 2023, 4:10 p.m. UTC | #7
Hi Philip,

On Wed, 18 Oct 2023 at 05:00, Philip Oberfichtner <pro@denx.de> wrote:
>
> Hi Heiko,
>
> On Wed, Oct 18, 2023 at 06:31:57AM +0200, Heiko Schocher wrote:
> > [...]
> >
> > May Philip can use uclass_get_device_by_phandle and try a list of
> > possible UCLASS candidates, like UCLASS_RTC, UCLASS_I2C_EEPROM,
> > UCLASS_POWER,... and if found, check if parent is UCLASS_I2C...
> >
> > may not so expensive ...
>
> Looks more cheap and elegant than the other variants indeed. But I'm not
> sure if we can maintain generality using this approach.
>
> What if the specific I2C driver is not included in the build, because
> the devices "actual" functionality is not required?

Then the device will not be bound and the function will fail. There needs
to be a node and a bound device.

> Or what if a driver
> for the specific device does not even exist in U-Boot?
>
> Wouldn't the device fail to probe then?

The DT should specify the compatible string so the correct i2c driver is
used. If there isn't one,I believe it uses I2C_GENERIC but you'll need to
check it.

>
> Please correct me if I'm wrong, I'd give it a shot in that case.
> Otherwise I'll try it with the aforementioned
> device_find_global_by_ofnode() followed by device_bind_driver() using
> UCLASS_I2C_GENERIC (I hope that works).

That is the same approach, I think. But anyway I think Heiko knows more
about this than me.

Regards,
Simon
Heiko Schocher Oct. 20, 2023, 3:18 a.m. UTC | #8
Hello Philip,

On 18.10.23 18:10, Simon Glass wrote:
> Hi Philip,
> 
> On Wed, 18 Oct 2023 at 05:00, Philip Oberfichtner <pro@denx.de <mailto:pro@denx.de>> wrote:
>>
>> Hi Heiko,
>>
>> On Wed, Oct 18, 2023 at 06:31:57AM +0200, Heiko Schocher wrote:
>> > [...]
>> >
>> > May Philip can use uclass_get_device_by_phandle and try a list of
>> > possible UCLASS candidates, like UCLASS_RTC, UCLASS_I2C_EEPROM,
>> > UCLASS_POWER,... and if found, check if parent is UCLASS_I2C...
>> >
>> > may not so expensive ...
>>
>> Looks more cheap and elegant than the other variants indeed. But I'm not
>> sure if we can maintain generality using this approach.
>>
>> What if the specific I2C driver is not included in the build, because
>> the devices "actual" functionality is not required?
> 
> Then the device will not be bound and the function will fail. There needs to be a node and a bound
> device.

Yep.

>> Or what if a driver
>> for the specific device does not even exist in U-Boot?
>>
>> Wouldn't the device fail to probe then?
> 
> The DT should specify the compatible string so the correct i2c driver is used. If there isn't one,I
> believe it uses I2C_GENERIC but you'll need to check it.

Yes, needs a check.

But... that is the real issue with that approach (more or less with
all bootcounter drivers?) ... if you assume that the device driver
is not there, may there is missing device init/probe stuff, so also
may the bootcounter will not work!

The i2c device should know, that some registers, mem (or whatever) is
used for bootcounter function, so that it does not use this space for
other device stuff ... to make bootcounters correct, we need something
like a MFD approach here ... and register a bootcounter within that...
so init/probe stuff is always done, and registers/mem is reserved ...

But unfortunately we do not have this...

>> Please correct me if I'm wrong, I'd give it a shot in that case.
>> Otherwise I'll try it with the aforementioned
>> device_find_global_by_ofnode() followed by device_bind_driver() using
>> UCLASS_I2C_GENERIC (I hope that works).
> 
> That is the same approach, I think. But anyway I think Heiko knows more about this than me.

Hmm... yes, with UCLASS_I2C_GENERIC it could also work, but see my
comment above ...

bye,
Heiko
diff mbox series

Patch

diff --git a/drivers/bootcount/Kconfig b/drivers/bootcount/Kconfig
index 7a2548ace2..1cf1de2b6d 100644
--- a/drivers/bootcount/Kconfig
+++ b/drivers/bootcount/Kconfig
@@ -109,6 +109,16 @@  config DM_BOOTCOUNT_RTC
 	  Accesses to the backing store are performed using the write16
 	  and read16 ops of DM RTC devices.
 
+config DM_BOOTCOUNT_I2C
+	bool "Driver Model boot counter on I2C device"
+	depends on DM_I2C
+	help
+	  Enable support for the bootcounter on a generic i2c device, like a
+	  RTC or PMIC. This requires an 'i2c-bus', the i2c chip 'address' and
+	  the 'offset' to read to and write from. All of the three settings are
+	  defined as device tree properties using the "u-boot,bootcount-i2c"
+	  compatible string.
+
 config DM_BOOTCOUNT_I2C_EEPROM
 	bool "Support i2c eeprom devices as a backing store for bootcount"
 	depends on I2C_EEPROM
diff --git a/drivers/bootcount/Makefile b/drivers/bootcount/Makefile
index d6d2389c16..e7771f5b36 100644
--- a/drivers/bootcount/Makefile
+++ b/drivers/bootcount/Makefile
@@ -13,5 +13,6 @@  obj-$(CONFIG_DM_BOOTCOUNT)      += bootcount-uclass.o
 obj-$(CONFIG_DM_BOOTCOUNT_PMIC_PFUZE100) += pmic_pfuze100.o
 obj-$(CONFIG_DM_BOOTCOUNT_RTC)  += rtc.o
 obj-$(CONFIG_DM_BOOTCOUNT_I2C_EEPROM)	+= i2c-eeprom.o
+obj-$(CONFIG_DM_BOOTCOUNT_I2C)	+= bootcount_dm_i2c.o
 obj-$(CONFIG_DM_BOOTCOUNT_SPI_FLASH)	+= spi-flash.o
 obj-$(CONFIG_DM_BOOTCOUNT_SYSCON) += bootcount_syscon.o
diff --git a/drivers/bootcount/bootcount_dm_i2c.c b/drivers/bootcount/bootcount_dm_i2c.c
new file mode 100644
index 0000000000..227641f77e
--- /dev/null
+++ b/drivers/bootcount/bootcount_dm_i2c.c
@@ -0,0 +1,126 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2023
+ * Philip Richard Oberfichtner <pro@denx.de>
+ *
+ * Based on previous work from Heiko Schocher (legacy bootcount_i2c.c driver)
+ */
+
+#include <bootcount.h>
+#include <common.h>
+#include <dm.h>
+#include <i2c.h>
+
+#define BC_MAGIC	0x55
+
+struct bootcount_i2c_priv {
+	struct udevice *chip;
+	unsigned int offset;
+};
+
+static int bootcount_i2c_set(struct udevice *dev, const u32 val)
+{
+	int ret;
+	struct bootcount_i2c_priv *priv = dev_get_priv(dev);
+
+	ret = dm_i2c_reg_write(priv->chip, priv->offset, BC_MAGIC);
+	if (ret < 0)
+		goto err_exit;
+
+	ret = dm_i2c_reg_write(priv->chip, priv->offset + 1, val & 0xff);
+	if (ret < 0)
+		goto err_exit;
+
+	return 0;
+
+err_exit:
+	log_debug("%s: Error writing to I2C device (%d)\n", __func__, ret);
+	return ret;
+}
+
+static int bootcount_i2c_get(struct udevice *dev, u32 *val)
+{
+	int ret;
+	struct bootcount_i2c_priv *priv = dev_get_priv(dev);
+
+	ret = dm_i2c_reg_read(priv->chip, priv->offset);
+	if (ret < 0)
+		goto err_exit;
+
+	if ((ret & 0xff) != BC_MAGIC) {
+		log_debug("%s: Invalid Magic, reset bootcounter.\n", __func__);
+		*val = 0;
+		return bootcount_i2c_set(dev, 0);
+	}
+
+	ret = dm_i2c_reg_read(priv->chip, priv->offset + 1);
+	if (ret < 0)
+		goto err_exit;
+
+	*val = ret;
+	return 0;
+
+err_exit:
+	log_debug("%s: Error reading from I2C device (%d)\n", __func__, ret);
+	return ret;
+}
+
+static int bootcount_i2c_probe(struct udevice *dev)
+{
+	struct bootcount_i2c_priv *priv = dev_get_priv(dev);
+	struct ofnode_phandle_args phandle_args;
+	struct udevice *bus;
+	unsigned int addr;
+	int ret;
+
+	ret = dev_read_u32(dev, "offset", &priv->offset);
+	if (ret) {
+		log_debug("%s: Unable to get offset\n", __func__);
+		return ret;
+	}
+
+	ret = dev_read_u32(dev, "address", &addr);
+	if (ret) {
+		log_debug("%s: Unable to get chip address\n", __func__);
+		return ret;
+	}
+
+	ret = dev_read_phandle_with_args(dev, "i2c-bus", NULL, 0, 0, &phandle_args);
+	if (ret) {
+		log_debug("%s: Unable to get phandle\n", __func__);
+		return ret;
+	}
+
+	ret = uclass_get_device_by_ofnode(UCLASS_I2C, phandle_args.node, &bus);
+	if (ret) {
+		log_debug("%s: Unable to get i2c bus\n", __func__);
+		return ret;
+	}
+
+	ret = i2c_get_chip(bus, addr, 1, &priv->chip);
+	if (ret) {
+		log_debug("%s: Unable to get i2c chip\n", __func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct bootcount_ops bootcount_i2c_ops = {
+	.get = bootcount_i2c_get,
+	.set = bootcount_i2c_set,
+};
+
+static const struct udevice_id bootcount_i2c_ids[] = {
+	{ .compatible = "u-boot,bootcount-i2c" },
+	{ }
+};
+
+U_BOOT_DRIVER(bootcount_i2c) = {
+	.name		= "bootcount-i2c",
+	.id		= UCLASS_BOOTCOUNT,
+	.priv_auto	= sizeof(struct bootcount_i2c_priv),
+	.probe		= bootcount_i2c_probe,
+	.of_match	= bootcount_i2c_ids,
+	.ops		= &bootcount_i2c_ops,
+};