diff --git a/debian/changelog b/debian/changelog
index fcf1ba210d6265f4bf667f35826c8b5bd95f87b8..7211c2c1fcef98833b1a0b124d5a8a9e06029eab 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+reform-tools (1.10) reform; urgency=medium
+
+  * add reform2-lpc-dkms package with the reform2_lpc module
+
+ -- Johannes Schauer Marin Rodrigues <josch@debian.org>  Mon, 12 Sep 2022 16:06:20 +0200
+
 reform-tools (1.9) reform; urgency=medium
 
   * install /usr/share/X11/xorg.conf.d/10-reform-etnaviv.conf
diff --git a/debian/control b/debian/control
index 42fa2768e06b4d5cc70fe86374aa368bad09db25..3fa93230e756635f22206a8d21d3a23dd8dcd6d3 100644
--- a/debian/control
+++ b/debian/control
@@ -5,7 +5,7 @@ Maintainer: Lukas F. Hartmann <lukas@mntre.com>
 Uploaders: Johannes Schauer Marin Rodrigues <josch@debian.org>
 Homepage: https://source.mnt.re/reform/reform-tools/
 Standards-Version: 4.6.0.1
-Build-Depends: debhelper-compat (= 13)
+Build-Depends: debhelper-compat (= 13), dh-sequence-dkms
 Rules-Requires-Root: no
 
 Package: reform-tools
@@ -19,6 +19,7 @@ Depends: alsa-utils,
          pavucontrol,
          procps,
          python3,
+         reform2-lpc-dkms,
          rsync,
          systemd,
          tzdata,
@@ -29,3 +30,10 @@ Description: MNT Reform System Tools
  /sbin/reform-init, a boot manager script that gets loaded by u-boot and which
  in turn launches the real /sbin/init after mounting an (encrypted) boot
  medium.
+
+Package: reform2-lpc-dkms
+Architecture: all
+Depends: ${misc:Depends}
+Description: dkms driver sources for reform2 lpc
+ This package provides sources for the reform2_lpc kernel module for use with
+ the Dynamic Kernel Module Support (dkms) framework.
diff --git a/debian/reform2-lpc-dkms.dkms b/debian/reform2-lpc-dkms.dkms
new file mode 100644
index 0000000000000000000000000000000000000000..08fd8a09459c20698f50d7282fa510e63be0c899
--- /dev/null
+++ b/debian/reform2-lpc-dkms.dkms
@@ -0,0 +1,4 @@
+PACKAGE_NAME="reform2_lpc"
+PACKAGE_VERSION="#MODULE_VERSION#"
+BUILT_MODULE_NAME[0]="reform2_lpc"
+DEST_MODULE_LOCATION[0]="/extra"
diff --git a/debian/reform2-lpc-dkms.install b/debian/reform2-lpc-dkms.install
new file mode 100644
index 0000000000000000000000000000000000000000..27a945d0d0d2b0681e2e89f8ba9a60fe5b03ec36
--- /dev/null
+++ b/debian/reform2-lpc-dkms.install
@@ -0,0 +1,2 @@
+lpc/reform2_lpc.c /usr/src/reform2_lpc-${env:DEB_VERSION_UPSTREAM}
+lpc/Makefile      /usr/src/reform2_lpc-${env:DEB_VERSION_UPSTREAM}
diff --git a/debian/reform2-lpc-dkms.triggers b/debian/reform2-lpc-dkms.triggers
new file mode 100644
index 0000000000000000000000000000000000000000..6c9f4543c2221c9e7e3093fa1a4079ed6a38c16c
--- /dev/null
+++ b/debian/reform2-lpc-dkms.triggers
@@ -0,0 +1 @@
+activate update-initramfs
diff --git a/debian/rules b/debian/rules
index 5de54cb8d2c9e437dcba1384fdab7801485e4136..91ef525ea53ee2afbda02f55669cc4f7ad256ab1 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,5 +1,9 @@
 #!/usr/bin/make -f
 
+# for debian/reform-tools.install and for dkms
+include /usr/share/dpkg/pkg-info.mk
+export DEB_VERSION_UPSTREAM
+
 %:
 	dh $@
 
@@ -9,3 +13,6 @@ execute_after_dh_auto_build:
 override_dh_installsystemd:
 	dh_installsystemd --name=reform-hw-setup
 	dh_installsystemd --name=reform-sleep
+
+override_dh_dkms:
+	dh_dkms -V $(DEB_VERSION_UPSTREAM)
diff --git a/lpc/Makefile b/lpc/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..ff72dae8f18c578042dbead54ea6d10065b7f249
--- /dev/null
+++ b/lpc/Makefile
@@ -0,0 +1,8 @@
+obj-m := reform2_lpc.o
+KERNEL_DIR = /lib/modules/$(shell uname -r)/build
+
+all:
+	$(MAKE) -C "$(KERNEL_DIR)" M="$(PWD)" modules
+
+clean:
+	$(MAKE) -C "$(KERNEL_DIR)" M="$(PWD)" clean
diff --git a/lpc/reform2_lpc.c b/lpc/reform2_lpc.c
new file mode 100644
index 0000000000000000000000000000000000000000..b3223f94687d13f5cd26785b211d7000a5a64a05
--- /dev/null
+++ b/lpc/reform2_lpc.c
@@ -0,0 +1,439 @@
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+
+static int lpcProbe(struct spi_device *spi);
+static void lpcRemove(struct spi_device *spi);
+static ssize_t showStatus(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t showCells(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t showFirmware(struct device *dev, struct device_attribute *attr, char *buf);
+static ssize_t showCapacity(struct device *dev, struct device_attribute *attr, char *buf);
+
+static ssize_t lpcCommand(struct device *dev, char command, uint8_t arg1, uint8_t *response);
+static int getBatProperty(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val);
+
+typedef struct lpc_driver_data
+{
+    struct spi_device *spi;
+    struct power_supply *bat;
+    struct mutex lock;
+} lpc_driver_data;
+
+static DEVICE_ATTR(status, 0444, showStatus, NULL);
+static DEVICE_ATTR(cells, 0444, showCells, NULL);
+static DEVICE_ATTR(firmware, 0444, showFirmware, NULL);
+static DEVICE_ATTR(capacity, 0444, showCapacity, NULL);
+
+static struct spi_board_info g_spi_board_info = {
+    .modalias = "reform2_lpc",
+    .max_speed_hz = 400000,
+    .bus_num = 0,
+    .chip_select = 0,
+    .mode = SPI_MODE_1,
+};
+
+static enum power_supply_property bat_props[] = {
+    POWER_SUPPLY_PROP_STATUS,
+    POWER_SUPPLY_PROP_TECHNOLOGY,
+    POWER_SUPPLY_PROP_VOLTAGE_NOW,
+    POWER_SUPPLY_PROP_CURRENT_NOW,
+    POWER_SUPPLY_PROP_CAPACITY,
+    POWER_SUPPLY_PROP_CHARGE_FULL,
+    POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+    POWER_SUPPLY_PROP_CHARGE_NOW,
+    POWER_SUPPLY_PROP_CHARGE_EMPTY,
+};
+
+static struct power_supply_desc bat_desc = {
+    .name = "8xlifepo4",
+    .properties = bat_props,
+    .num_properties = ARRAY_SIZE(bat_props),
+    .get_property = getBatProperty,
+    .type = POWER_SUPPLY_TYPE_BATTERY,
+};
+
+static struct power_supply_config psy_cfg = {};
+
+static int lpcProbe(struct spi_device *spi)
+{
+    struct lpc_driver_data *data;
+    int ret;
+
+    printk(KERN_INFO "%s: probing ...\n", "reform2_lpc");
+
+    spi->max_speed_hz = g_spi_board_info.max_speed_hz;
+    spi->mode = g_spi_board_info.mode;
+    spi->bits_per_word = 8;
+
+    ret = spi_setup(spi);
+    if (ret)
+    {
+        printk(KERN_ERR "%s: spi_setup failed\n", __func__);
+        return -ENODEV;
+    }
+
+    data = kzalloc(sizeof(struct lpc_driver_data), GFP_KERNEL);
+    if (data == NULL)
+    {
+        printk(KERN_ERR "%s: kzalloc failed\n", __func__);
+        return -ENOMEM;
+    }
+
+    data->spi = spi;
+    mutex_init(&data->lock);
+    spi_set_drvdata(spi, data);
+
+    ret = device_create_file(&spi->dev, &dev_attr_status);
+    if (ret)
+    {
+        printk(KERN_ERR "%s: device_create_file failed\n", __func__);
+    }
+
+    ret = device_create_file(&spi->dev, &dev_attr_cells);
+    if (ret)
+    {
+        printk(KERN_ERR "%s: device_create_file failed\n", __func__);
+    }
+
+    ret = device_create_file(&spi->dev, &dev_attr_firmware);
+    if (ret)
+    {
+        printk(KERN_ERR "%s: device_create_file failed\n", __func__);
+    }
+
+    ret = device_create_file(&spi->dev, &dev_attr_capacity);
+    if (ret)
+    {
+        printk(KERN_ERR "%s: device_create_file failed\n", __func__);
+    }
+
+    psy_cfg.of_node = spi->dev.of_node,
+    psy_cfg.drv_data = data,
+
+    data->bat = power_supply_register_no_ws(&spi->dev, &bat_desc, &psy_cfg);
+    if (IS_ERR(data->bat))
+    {
+        printk(KERN_ERR "%s: power_supply_register_no_ws failed\n", __func__);
+        return PTR_ERR(data->bat);
+    }
+
+    return ret;
+}
+
+static void lpcRemove(struct spi_device *spi)
+{
+    struct lpc_driver_data *data = (struct lpc_driver_data *)spi_get_drvdata(spi);
+
+    printk(KERN_INFO "%s: removing ... \n", "reform2_lpc");
+
+    device_remove_file(&spi->dev, &dev_attr_status);
+    device_remove_file(&spi->dev, &dev_attr_firmware);
+    device_remove_file(&spi->dev, &dev_attr_cells);
+    device_remove_file(&spi->dev, &dev_attr_capacity);
+
+    power_supply_unregister(data->bat);
+
+    kfree(data);
+}
+
+static ssize_t showStatus(struct device *dev, struct device_attribute *attr, char *buf)
+{
+    uint8_t buffer[8];
+    int16_t voltage;
+    int16_t amps;
+    uint8_t percentage;
+    uint8_t status;
+    int ret = 0;
+
+    ret = lpcCommand(dev, 'q', 0, buffer);
+    if (ret)
+    {
+        printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+    }
+
+    voltage = (int16_t)buffer[0] | ((int16_t)buffer[1] << 8);
+    amps = (int16_t)buffer[2] | ((int16_t)buffer[3] << 8);
+    percentage = buffer[4];
+    status = buffer[5];
+
+    return snprintf(buf, PAGE_SIZE, "%d.%d %d.%d %2d%% %d",
+                    voltage / 1000, voltage % 1000, 
+                    amps / 1000, abs(amps % 1000), 
+                    percentage, status);
+}
+
+static ssize_t showCells(struct device *dev, struct device_attribute *attr, char *buf)
+{
+    uint8_t buffer[8];
+    uint16_t cells[8];
+    ssize_t wroteChars = 0;
+    int ret = 0;
+
+    ret = lpcCommand(dev, 'v', 0, buffer);
+    if (ret)
+    {
+        printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+    }
+
+    for(uint8_t s = 0; s < 4; s++)
+    {
+        cells[s] = buffer[s*2] | buffer[(s*2)+1] << 8;
+    }
+    
+    ret = lpcCommand(dev, 'v', 1, buffer);
+    if (ret)
+    {
+        printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+    }
+
+    for(uint8_t s = 0; s < 4; s++)
+    {
+        cells[s+4] = buffer[s*2] | buffer[(s*2)+1] << 8;
+    }
+
+    for(uint8_t s = 0; s < 8; s++)
+    {
+        ret = snprintf(buf + wroteChars, PAGE_SIZE - wroteChars, "%d.%d ", cells[s]/1000, cells[s]%1000);
+        if(ret != -1)
+        {
+            wroteChars += ret;
+        }
+    }
+
+    // drop the trailing whitespace
+    if(wroteChars > 0)
+    {
+        wroteChars--;
+    }
+    
+    return wroteChars;
+}
+
+static ssize_t showFirmware(struct device *dev, struct device_attribute *attr, char *buf)
+{
+    uint8_t str1[9];
+    uint8_t str2[9];
+    uint8_t str3[9];
+    int ret = 0;
+    
+    ret = lpcCommand(dev, 'f', 0, str1);
+    if (ret)
+    {
+        printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+    }
+
+    ret = lpcCommand(dev, 'f', 1, str2);
+    if (ret)
+    {
+        printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+    }
+
+    ret = lpcCommand(dev, 'f', 2, str3);
+    if (ret)
+    {
+        printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+    }
+
+    str1[8] = '\0';
+    str2[8] = '\0';
+    str3[8] = '\0';
+
+    return snprintf(buf, PAGE_SIZE, "%s %s %s", str1, str2, str3);
+}
+
+static ssize_t showCapacity(struct device *dev, struct device_attribute *attr, char *buf)
+{
+    uint8_t buffer[8];
+    int ret = 0;
+    uint16_t cap_accu_mah, cap_min_mah, cap_max_mah;
+    ret = lpcCommand(dev, 'c', 0, buffer);
+    if (ret)
+    {
+        printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+    }
+
+    cap_accu_mah = buffer[0] | (buffer[1] << 8);
+    cap_min_mah =  buffer[2] | (buffer[3] << 8);
+    cap_max_mah = buffer[4] | (buffer[5] << 8);
+
+    return snprintf(buf, PAGE_SIZE, "%d %d %d", cap_accu_mah, cap_min_mah, cap_max_mah);
+}
+
+static ssize_t lpcCommand(struct device *dev, char command, uint8_t arg1, uint8_t *responseBuffer)
+{
+    struct lpc_driver_data *data = (struct lpc_driver_data *)dev_get_drvdata(dev);
+    uint8_t commandBuffer[4] = {0xB5, command, arg1, 0x0};
+    int ret = 0;
+
+    mutex_lock(&data->lock);
+    
+    ret = spi_write(data->spi, commandBuffer, 4);
+    if (ret)
+    {
+        printk(KERN_INFO "%s: spi_write failed\n", __func__);
+    }
+    msleep(50);
+
+    ret = spi_read(data->spi, responseBuffer, 8);
+    if (ret)
+    {
+        printk(KERN_INFO "%s: spi_read failed\n", __func__);
+    }
+    msleep(50);
+    mutex_unlock(&data->lock);
+
+    return ret;
+}
+
+static int getBatProperty(struct power_supply *psy,
+                    enum power_supply_property psp,
+                    union power_supply_propval *val)
+{
+    int ret = 0;
+    uint8_t buffer[8];
+    struct lpc_driver_data *data;
+    struct device *dev;
+    int16_t amp;
+
+    data = (struct lpc_driver_data *)power_supply_get_drvdata(psy);
+    dev = &(data->spi->dev);
+
+    switch (psp) 
+    {
+        case POWER_SUPPLY_PROP_STATUS:
+            val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+            ret = lpcCommand(dev, 'q', 0, buffer);
+            if (ret)
+            {
+                printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+            }
+            amp = (int16_t)buffer[2] | ((int16_t)buffer[3] << 8);
+            if (amp < 0)
+            {
+                val->intval = POWER_SUPPLY_STATUS_CHARGING;
+            }
+            else if (amp == 0)
+            {
+                if (buffer[4] == 100)
+                {
+                    val->intval = POWER_SUPPLY_STATUS_FULL;
+                }
+                else
+                {
+                    val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+                }
+            }
+            else
+            {
+                val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+            }
+            break;
+            
+        case POWER_SUPPLY_PROP_TECHNOLOGY:
+            val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe;
+            break;
+            
+        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+            ret = lpcCommand(dev, 'q', 0, buffer);
+            if (ret)
+            {
+                printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+                ret = -EINVAL;
+            }
+            val->intval = (buffer[0] | buffer[1] << 8) * 1000;
+            break;
+            
+        case POWER_SUPPLY_PROP_CURRENT_NOW:
+            ret = lpcCommand(dev, 'q', 0, buffer);
+            if (ret)
+            {
+                printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+                ret = -EINVAL;
+            }
+            amp = (int16_t)buffer[2] | ((int16_t)buffer[3] << 8);
+            // negative current, battery is charging
+            // reporting a negative value is out of spec
+            if(amp < 0)
+            {
+                amp = 0;
+            }
+            val->intval = amp * 1000;
+            break;
+            
+        case POWER_SUPPLY_PROP_CAPACITY:
+            ret = lpcCommand(dev, 'q', 0, buffer);
+            if (ret)
+            {
+                printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+                ret = -EINVAL;
+            }
+            val->intval = buffer[4];
+            break;
+            
+        case POWER_SUPPLY_PROP_CHARGE_FULL:
+        case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+            ret = lpcCommand(dev, 'c', 0, buffer);
+            if (ret)
+            {
+                printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+                ret = -EINVAL;
+            }
+            val->intval = (buffer[4] | buffer[5] << 8) * 1000;
+            break;
+            
+        case POWER_SUPPLY_PROP_CHARGE_NOW:
+            ret = lpcCommand(dev, 'c', 0, buffer);
+            if (ret)
+            {
+                printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+                ret = -EINVAL;
+            }
+            val->intval = (buffer[0] | buffer[1] << 8) * 1000;
+            break;
+            
+        case POWER_SUPPLY_PROP_CHARGE_EMPTY:
+            ret = lpcCommand(dev, 'c', 0, buffer);
+            if (ret)
+            {
+                printk(KERN_INFO "%s: lpcCommand failed\n", __func__);
+                ret = -EINVAL;
+            }
+            val->intval = (buffer[2] | buffer[3] << 8) * 1000;
+            break;
+            
+        default:
+            val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+            ret = -EINVAL;
+            break;
+    }
+    return ret;
+}
+
+static const struct of_device_id of_tis_spi_match[] = {
+    {.compatible = "mntre,lpc11u24", .data = 0},
+    {}};
+MODULE_DEVICE_TABLE(of, of_tis_spi_match);
+
+static struct spi_device_id g_spi_dev_id_list[] = {
+        { "lpc11u24", 0 },
+        { },
+};
+MODULE_DEVICE_TABLE(spi, g_spi_dev_id_list);
+
+static struct spi_driver g_spi_driver = {
+    .probe = lpcProbe,
+    .remove = lpcRemove,
+    .driver = {
+        .of_match_table = of_match_ptr(of_tis_spi_match),
+        .owner = THIS_MODULE,
+        .name = "reform2_lpc",
+    },
+    .id_table = g_spi_dev_id_list,
+};
+module_spi_driver(g_spi_driver);
+
+MODULE_DESCRIPTION("Reform 2 LPC Driver");
+MODULE_LICENSE("GPL");