Add a driver for the Sudden Motion Sensor accelerometer,
exposed through IIO. It reports the three axes and also
drop, orientation and shock events.

This replaces the motion sensor support in applesmc.

Signed-off-by: Atharva Tiwari <[email protected]>
---
 drivers/iio/accel/Kconfig        |  10 ++
 drivers/iio/accel/Makefile       |   1 +
 drivers/iio/accel/macsmc-accel.c | 248 +++++++++++++++++++++++++++++++
 drivers/mfd/macsmc-core.c        |  10 ++
 4 files changed, 269 insertions(+)
 create mode 100644 drivers/iio/accel/macsmc-accel.c

diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
index 141ac9842..50bc127a4 100644
--- a/drivers/iio/accel/Kconfig
+++ b/drivers/iio/accel/Kconfig
@@ -532,6 +532,16 @@ config KXCJK1013
          To compile this driver as a module, choose M here: the module will
          be called kxcjk-1013.
 
+config MACSMC_ACCEL
+       tristate "Apple SMC Motion Sensor Driver"
+       depends on MACSMC_ACPI
+       help
+         Say yes here if you want support for Apple Motion Sensors, that uses
+         the System Management Controller.
+
+         To compile this driver as a module, choose M here: the module will
+         be called macsmc-accel.
+
 config MC3230
        tristate "mCube MC3230 Digital Accelerometer Driver"
        depends on I2C
diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile
index fa440a859..52fe661de 100644
--- a/drivers/iio/accel/Makefile
+++ b/drivers/iio/accel/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_KXSD9)   += kxsd9.o
 obj-$(CONFIG_KXSD9_SPI)        += kxsd9-spi.o
 obj-$(CONFIG_KXSD9_I2C)        += kxsd9-i2c.o
 obj-$(CONFIG_MC3230)   += mc3230.o
+obj-$(CONFIG_MACSMC_ACCEL)     += macsmc-accel.o
 
 obj-$(CONFIG_MMA7455)          += mma7455_core.o
 obj-$(CONFIG_MMA7455_I2C)      += mma7455_i2c.o
diff --git a/drivers/iio/accel/macsmc-accel.c b/drivers/iio/accel/macsmc-accel.c
new file mode 100644
index 000000000..0abf81e7b
--- /dev/null
+++ b/drivers/iio/accel/macsmc-accel.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC sudden motion sensor (SMS) driver
+ *
+ * Copyright (C) 2026 Atharva Tiwari <[email protected]>
+ *
+ * Based on drivers/hwmon/applesmc.c:
+ * Copyright (C) 2007 Nicolas Boichat <[email protected]>
+ * Copyright (C) 2010 Henrik Rydberg <[email protected]>
+ */
+
+#include <linux/delay.h>
+#include <linux/byteorder/generic.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+
+struct macsmc_accel {
+       struct apple_smc *smc;
+       struct iio_dev *indio_dev;
+       struct notifier_block nb;
+};
+
+enum {
+       SMS_DROP = 0,
+       SMS_ORIENTATION,
+       SMS_SHOCK,
+};
+
+static int macsmc_accel_read(struct macsmc_accel *drv, smc_key key, s16 *p)
+{
+       int ret;
+
+       ret = apple_smc_read_s16(drv->smc, key, p);
+       if (ret < 0)
+               return ret;
+
+       *p = (s16)be16_to_cpu(*p);
+       return 0;
+}
+
+static int macsmc_accel_dev_init(struct macsmc_accel *drv)
+{
+       int ret, total;
+       u16 buf;
+
+       for (total = 0; total < 100; total++) {
+               ret = apple_smc_read_u16(drv->smc, SMC_KEY(MOCN), &buf);
+               if (!ret && buf)
+                       return 0;
+
+               buf = 0xE0;
+               apple_smc_write_u16(drv->smc, SMC_KEY(MOCN), buf);
+               msleep(50);
+       }
+
+       return -ENODEV;
+}
+
+static int macsmc_accel_read_raw(struct iio_dev *indio_dev, struct 
iio_chan_spec const *chan,
+               int *val, int *val2, long mask)
+{
+       struct macsmc_accel *drv = iio_priv(indio_dev);
+       int ret;
+       s16 buf;
+
+       if (mask != IIO_CHAN_INFO_RAW)
+               return -EINVAL;
+
+       switch (chan->channel2) {
+       case IIO_MOD_X:
+               ret = macsmc_accel_read(drv, SMC_KEY(MO_X), &buf);
+               if (ret < 0)
+                       return ret;
+
+               *val = buf;
+               return IIO_VAL_INT;
+       case IIO_MOD_Y:
+               ret = macsmc_accel_read(drv, SMC_KEY(MO_Y), &buf);
+               if (ret < 0)
+                       return ret;
+
+               *val = buf;
+               return IIO_VAL_INT;
+       case IIO_MOD_Z:
+               ret = macsmc_accel_read(drv, SMC_KEY(MO_Z), &buf);
+               if (ret < 0)
+                       return ret;
+
+               *val = buf;
+               return IIO_VAL_INT;
+       }
+
+       return -EINVAL;
+}
+
+static int macsmc_accel_event(struct notifier_block *nb, unsigned long event, 
void *data)
+{
+       struct macsmc_accel *drv = container_of(nb, struct macsmc_accel, nb);
+       u64 code;
+
+       /* check if event is for us */
+       if (event != 0x60 && event != 0x69 && event != 0x6F)
+               return NOTIFY_DONE;
+
+       switch (event) {
+       case 0x60:
+               code = IIO_UNMOD_EVENT_CODE(IIO_ACCEL, SMS_DROP,
+                                           IIO_EV_TYPE_CHANGE,
+                                           IIO_EV_DIR_NONE);
+               break;
+       case 0x69:
+               code = IIO_UNMOD_EVENT_CODE(IIO_ACCEL, SMS_ORIENTATION,
+                                           IIO_EV_TYPE_CHANGE,
+                                           IIO_EV_DIR_NONE);
+               break;
+       case 0x6F:
+               code = IIO_UNMOD_EVENT_CODE(IIO_ACCEL, SMS_SHOCK,
+                                           IIO_EV_TYPE_CHANGE,
+                                           IIO_EV_DIR_NONE);
+               break;
+       }
+
+       iio_push_event(drv->indio_dev, code, iio_get_time_ns(drv->indio_dev));
+       return NOTIFY_OK;
+}
+
+static void macsmc_accel_unregister_notifier(void *data)
+{
+       struct macsmc_accel *drv = data;
+
+       blocking_notifier_chain_unregister(drv->smc->event_handlers,
+                                       &drv->nb);
+}
+
+static const struct iio_event_spec macsmc_accel_event_spec = {
+       .type = IIO_EV_TYPE_CHANGE,
+       .dir = IIO_EV_DIR_NONE,
+       .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+};
+
+static const struct iio_chan_spec macsmc_accel_channels[] = {
+       {
+               .type = IIO_ACCEL,
+               .modified = 1,
+               .channel2 = IIO_MOD_X,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+       },
+       {
+               .type = IIO_ACCEL,
+               .modified = 1,
+               .channel2 = IIO_MOD_Y,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+       },
+       {
+               .type = IIO_ACCEL,
+               .modified = 1,
+               .channel2 = IIO_MOD_Z,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+       },
+       {
+               .type = IIO_ACCEL,
+               .channel = SMS_DROP,
+               .event_spec = &macsmc_accel_event_spec,
+               .num_event_specs = 1,
+       },
+       {
+               .type = IIO_ACCEL,
+               .channel = SMS_ORIENTATION,
+               .event_spec = &macsmc_accel_event_spec,
+               .num_event_specs = 1,
+       },
+       {
+               .type = IIO_ACCEL,
+               .channel = SMS_SHOCK,
+               .event_spec = &macsmc_accel_event_spec,
+               .num_event_specs = 1,
+       },
+};
+
+static const struct iio_info macsmc_accel_info = {
+       .read_raw = macsmc_accel_read_raw
+};
+
+static int macsmc_accel_restore(struct device *dev)
+{
+       struct macsmc_accel *drv = dev_get_drvdata(dev);
+
+       return macsmc_accel_dev_init(drv);
+}
+
+static const struct dev_pm_ops macsmc_accel_pm_ops = {
+       .restore = macsmc_accel_restore,
+};
+
+static int macsmc_accel_probe(struct platform_device *pdev)
+{
+       struct macsmc_accel *drv;
+       struct iio_dev *indio_dev;
+       int ret;
+
+       indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*drv));
+       if (!indio_dev)
+               return -ENOMEM;
+
+       drv = iio_priv(indio_dev);
+       drv->smc = dev_get_drvdata(pdev->dev.parent);
+
+       ret = macsmc_accel_dev_init(drv);
+       if (ret)
+               return ret;
+
+       drv->indio_dev = indio_dev;
+       drv->nb.notifier_call = macsmc_accel_event;
+       ret = blocking_notifier_chain_register(drv->smc->event_handlers, 
&drv->nb);
+       if (ret)
+               return ret;
+
+       ret = devm_add_action_or_reset(&pdev->dev, 
macsmc_accel_unregister_notifier, drv);
+       if (ret)
+               return ret;
+
+       platform_set_drvdata(pdev, drv);
+
+       indio_dev->name = "macsmc-accel";
+       indio_dev->modes = INDIO_DIRECT_MODE;
+       indio_dev->info = &macsmc_accel_info;
+       indio_dev->channels = macsmc_accel_channels;
+       indio_dev->num_channels = ARRAY_SIZE(macsmc_accel_channels);
+
+       return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static struct platform_driver macsmc_accel_driver = {
+       .probe = macsmc_accel_probe,
+       .driver = {
+               .name = "macsmc-accel",
+               .pm = &macsmc_accel_pm_ops,
+       },
+};
+module_platform_driver(macsmc_accel_driver);
+
+MODULE_AUTHOR("Atharva Tiwari <[email protected]>");
+MODULE_DESCRIPTION("Apple SMC Motion Sensor Driver");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_ALIAS("platform:macsmc-accel");
diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
index 6014df4fe..6567d2f1f 100644
--- a/drivers/mfd/macsmc-core.c
+++ b/drivers/mfd/macsmc-core.c
@@ -24,6 +24,10 @@ static const struct mfd_cell apple_smc_rtkit_devs[] = {
  * There isn't device tree on acpi backend, so we need to use
  * apple_smc_key_exists to register mfd devs
  */
+static const struct mfd_cell apple_smc_acpi_accel[] = {
+       MFD_CELL_NAME("macsmc-accel"),
+};
+
 static const struct mfd_cell apple_smc_acpi_common[] = {
        MFD_CELL_NAME("macsmc-hwmon"),
        MFD_CELL_NAME("macsmc-power"),
@@ -169,6 +173,12 @@ int apple_smc_probe(struct device *dev, const struct 
apple_smc_backend_ops *ops,
                                return ret;
                }
 
+               if (apple_smc_key_exists(smc, SMC_KEY(MOCN))) {
+                       ret = devm_mfd_add_devices(dev, -1, 
apple_smc_acpi_accel,
+                               ARRAY_SIZE(apple_smc_acpi_accel), NULL, 0, 
NULL);
+                       if (ret < 0)
+                               return ret;
+               }
        } else
                ret = devm_mfd_add_devices(dev, -1, apple_smc_rtkit_devs,
                        ARRAY_SIZE(apple_smc_rtkit_devs), NULL, 0, NULL);
-- 
2.55.0


Reply via email to