linux/drivers/iio/accel/bmc150-accel-i2c.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * 3-axis accelerometer driver supporting many I2C Bosch-Sensortec chips
   4 * Copyright (c) 2014, Intel Corporation.
   5 */
   6
   7#include <linux/device.h>
   8#include <linux/mod_devicetable.h>
   9#include <linux/i2c.h>
  10#include <linux/module.h>
  11#include <linux/acpi.h>
  12#include <linux/regmap.h>
  13
  14#include "bmc150-accel.h"
  15
  16#ifdef CONFIG_ACPI
  17static const struct acpi_device_id bmc150_acpi_dual_accel_ids[] = {
  18        {"BOSC0200"},
  19        {"DUAL250E"},
  20        { }
  21};
  22
  23/*
  24 * The DUAL250E ACPI device for 360° hinges type 2-in-1s with 1 accelerometer
  25 * in the display and 1 in the hinge has an ACPI-method (DSM) to tell the
  26 * ACPI code about the angle between the 2 halves. This will make the ACPI
  27 * code enable/disable the keyboard and touchpad. We need to call this to avoid
  28 * the keyboard being disabled when the 2-in-1 is turned-on or resumed while
  29 * fully folded into tablet mode (which gets detected with a HALL-sensor).
  30 * If we don't call this then the keyboard won't work even when the 2-in-1 is
  31 * changed to be used in laptop mode after the power-on / resume.
  32 *
  33 * This DSM takes 2 angles, selected by setting aux0 to 0 or 1, these presumably
  34 * define the angle between the gravity vector measured by the accelerometer in
  35 * the display (aux0=0) resp. the base (aux0=1) and some reference vector.
  36 * The 2 angles get subtracted from each other so the reference vector does
  37 * not matter and we can simply leave the second angle at 0.
  38 */
  39
  40#define BMC150_DSM_GUID                         "7681541e-8827-4239-8d9d-36be7fe12542"
  41#define DUAL250E_SET_ANGLE_FN_INDEX             3
  42
  43struct dual250e_set_angle_args {
  44        u32 aux0;
  45        u32 ang0;
  46        u32 rawx;
  47        u32 rawy;
  48        u32 rawz;
  49} __packed;
  50
  51static bool bmc150_acpi_set_angle_dsm(struct i2c_client *client, u32 aux0, u32 ang0)
  52{
  53        struct acpi_device *adev = ACPI_COMPANION(&client->dev);
  54        struct dual250e_set_angle_args args = {
  55                .aux0 = aux0,
  56                .ang0 = ang0,
  57        };
  58        union acpi_object args_obj, *obj;
  59        guid_t guid;
  60
  61        if (!acpi_dev_hid_uid_match(adev, "DUAL250E", NULL))
  62                return false;
  63
  64        guid_parse(BMC150_DSM_GUID, &guid);
  65
  66        if (!acpi_check_dsm(adev->handle, &guid, 0, BIT(DUAL250E_SET_ANGLE_FN_INDEX)))
  67                return false;
  68
  69        /*
  70         * Note this triggers the following warning:
  71         * "ACPI Warning: \_SB.PCI0.I2C2.ACC1._DSM: Argument #4 type mismatch -
  72         *                Found [Buffer], ACPI requires [Package]"
  73         * This is unavoidable since the _DSM implementation expects a "naked"
  74         * buffer, so wrapping it in a package will _not_ work.
  75         */
  76        args_obj.type = ACPI_TYPE_BUFFER;
  77        args_obj.buffer.length = sizeof(args);
  78        args_obj.buffer.pointer = (u8 *)&args;
  79
  80        obj = acpi_evaluate_dsm(adev->handle, &guid, 0, DUAL250E_SET_ANGLE_FN_INDEX, &args_obj);
  81        if (!obj) {
  82                dev_err(&client->dev, "Failed to call DSM to enable keyboard and touchpad\n");
  83                return false;
  84        }
  85
  86        ACPI_FREE(obj);
  87        return true;
  88}
  89
  90static bool bmc150_acpi_enable_keyboard(struct i2c_client *client)
  91{
  92        /*
  93         * The EC must see a change for it to re-enable the kbd, so first
  94         * set the angle to 270° (tent/stand mode) and then change it to
  95         * 90° (laptop mode).
  96         */
  97        if (!bmc150_acpi_set_angle_dsm(client, 0, 270))
  98                return false;
  99
 100        /* The EC needs some time to notice the angle being changed */
 101        msleep(100);
 102
 103        return bmc150_acpi_set_angle_dsm(client, 0, 90);
 104}
 105
 106static void bmc150_acpi_resume_work(struct work_struct *work)
 107{
 108        struct bmc150_accel_data *data =
 109                container_of(work, struct bmc150_accel_data, resume_work.work);
 110
 111        bmc150_acpi_enable_keyboard(data->second_device);
 112}
 113
 114static void bmc150_acpi_resume_handler(struct device *dev)
 115{
 116        struct bmc150_accel_data *data = iio_priv(dev_get_drvdata(dev));
 117
 118        /*
 119         * Delay the bmc150_acpi_enable_keyboard() call till after the system
 120         * resume has completed, otherwise it will not work.
 121         */
 122        schedule_delayed_work(&data->resume_work, msecs_to_jiffies(1000));
 123}
 124
 125/*
 126 * Some acpi_devices describe 2 accelerometers in a single ACPI device,
 127 * try instantiating a second i2c_client for an I2cSerialBusV2 ACPI resource
 128 * with index 1.
 129 */
 130static void bmc150_acpi_dual_accel_probe(struct i2c_client *client)
 131{
 132        struct bmc150_accel_data *data = iio_priv(i2c_get_clientdata(client));
 133        struct acpi_device *adev = ACPI_COMPANION(&client->dev);
 134        char dev_name[16];
 135        struct i2c_board_info board_info = {
 136                .type = "bmc150_accel",
 137                .dev_name = dev_name,
 138                .fwnode = client->dev.fwnode,
 139        };
 140
 141        if (acpi_match_device_ids(adev, bmc150_acpi_dual_accel_ids))
 142                return;
 143
 144        /*
 145         * The 2nd accel sits in the base of 2-in-1s. The suffix is static, as
 146         * there should never be more then 1 ACPI node with 2 accelerometers.
 147         */
 148        snprintf(dev_name, sizeof(dev_name), "%s:base", acpi_device_hid(adev));
 149
 150        board_info.irq = acpi_dev_gpio_irq_get(adev, 1);
 151
 152        data->second_device = i2c_acpi_new_device(&client->dev, 1, &board_info);
 153
 154        if (!IS_ERR(data->second_device) && bmc150_acpi_enable_keyboard(data->second_device)) {
 155                INIT_DELAYED_WORK(&data->resume_work, bmc150_acpi_resume_work);
 156                data->resume_callback = bmc150_acpi_resume_handler;
 157        }
 158}
 159
 160static void bmc150_acpi_dual_accel_remove(struct i2c_client *client)
 161{
 162        struct bmc150_accel_data *data = iio_priv(i2c_get_clientdata(client));
 163
 164        if (data->resume_callback)
 165                cancel_delayed_work_sync(&data->resume_work);
 166
 167        i2c_unregister_device(data->second_device);
 168}
 169#else
 170static void bmc150_acpi_dual_accel_probe(struct i2c_client *client) {}
 171static void bmc150_acpi_dual_accel_remove(struct i2c_client *client) {}
 172#endif
 173
 174static int bmc150_accel_probe(struct i2c_client *client,
 175                              const struct i2c_device_id *id)
 176{
 177        struct regmap *regmap;
 178        const char *name = NULL;
 179        enum bmc150_type type = BOSCH_UNKNOWN;
 180        bool block_supported =
 181                i2c_check_functionality(client->adapter, I2C_FUNC_I2C) ||
 182                i2c_check_functionality(client->adapter,
 183                                        I2C_FUNC_SMBUS_READ_I2C_BLOCK);
 184        int ret;
 185
 186        regmap = devm_regmap_init_i2c(client, &bmc150_regmap_conf);
 187        if (IS_ERR(regmap)) {
 188                dev_err(&client->dev, "Failed to initialize i2c regmap\n");
 189                return PTR_ERR(regmap);
 190        }
 191
 192        if (id) {
 193                name = id->name;
 194                type = id->driver_data;
 195        }
 196
 197        ret = bmc150_accel_core_probe(&client->dev, regmap, client->irq,
 198                                      type, name, block_supported);
 199        if (ret)
 200                return ret;
 201
 202        /*
 203         * The !id check avoids recursion when probe() gets called
 204         * for the second client.
 205         */
 206        if (!id && has_acpi_companion(&client->dev))
 207                bmc150_acpi_dual_accel_probe(client);
 208
 209        return 0;
 210}
 211
 212static int bmc150_accel_remove(struct i2c_client *client)
 213{
 214        bmc150_acpi_dual_accel_remove(client);
 215
 216        return bmc150_accel_core_remove(&client->dev);
 217}
 218
 219static const struct acpi_device_id bmc150_accel_acpi_match[] = {
 220        {"BMA0255"},
 221        {"BMA0280"},
 222        {"BMA222"},
 223        {"BMA222E"},
 224        {"BMA250E"},
 225        {"BMC150A"},
 226        {"BMI055A"},
 227        {"BOSC0200"},
 228        {"BSBA0150"},
 229        {"DUAL250E"},
 230        { },
 231};
 232MODULE_DEVICE_TABLE(acpi, bmc150_accel_acpi_match);
 233
 234static const struct i2c_device_id bmc150_accel_id[] = {
 235        {"bma222"},
 236        {"bma222e"},
 237        {"bma250e"},
 238        {"bma253"},
 239        {"bma254"},
 240        {"bma255"},
 241        {"bma280"},
 242        {"bmc150_accel"},
 243        {"bmc156_accel", BOSCH_BMC156},
 244        {"bmi055_accel"},
 245        {}
 246};
 247
 248MODULE_DEVICE_TABLE(i2c, bmc150_accel_id);
 249
 250static const struct of_device_id bmc150_accel_of_match[] = {
 251        { .compatible = "bosch,bma222" },
 252        { .compatible = "bosch,bma222e" },
 253        { .compatible = "bosch,bma250e" },
 254        { .compatible = "bosch,bma253" },
 255        { .compatible = "bosch,bma254" },
 256        { .compatible = "bosch,bma255" },
 257        { .compatible = "bosch,bma280" },
 258        { .compatible = "bosch,bmc150_accel" },
 259        { .compatible = "bosch,bmc156_accel" },
 260        { .compatible = "bosch,bmi055_accel" },
 261        { },
 262};
 263MODULE_DEVICE_TABLE(of, bmc150_accel_of_match);
 264
 265static struct i2c_driver bmc150_accel_driver = {
 266        .driver = {
 267                .name   = "bmc150_accel_i2c",
 268                .of_match_table = bmc150_accel_of_match,
 269                .acpi_match_table = ACPI_PTR(bmc150_accel_acpi_match),
 270                .pm     = &bmc150_accel_pm_ops,
 271        },
 272        .probe          = bmc150_accel_probe,
 273        .remove         = bmc150_accel_remove,
 274        .id_table       = bmc150_accel_id,
 275};
 276module_i2c_driver(bmc150_accel_driver);
 277
 278MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
 279MODULE_LICENSE("GPL v2");
 280MODULE_DESCRIPTION("BMC150 I2C accelerometer driver");
 281