// SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0-or-later /* * ENE KB3930 Embedded Controller Driver * * Copyright (C) 2020 Lubomir Rintel */ #include #include #include #include #include #include #include /* I2C registers that are multiplexing access to the EC RAM. */ enum { EC_DATA_IN = 0x00, EC_RAM_OUT = 0x80, EC_RAM_IN = 0x81, }; /* EC RAM registers. */ enum { EC_MODEL = 0x30, EC_VERSION_MAJ = 0x31, EC_VERSION_MIN = 0x32, }; struct kb3930 { struct i2c_client *client; struct regmap *ram_regmap; struct gpio_descs *off_gpios; }; static struct kb3930 *kb3930_power_off; #define EC_GPIO_WAVE 0 #define EC_GPIO_OFF_MODE 1 #define EC_OFF_MODE_REBOOT 0 #define EC_OFF_MODE_POWER 1 static void kb3930_off(struct kb3930 *ddata, int off_mode) { gpiod_direction_output(ddata->off_gpios->desc[EC_GPIO_OFF_MODE], off_mode); /* * This creates a 10 Hz wave on EC_GPIO_WAVE that signals a * shutdown request to the EC. Once the EC detects it, it will * proceed to turn the power off or reset the board depending on * the value of EC_GPIO_OFF_MODE. */ while (1) { mdelay(50); gpiod_direction_output(ddata->off_gpios->desc[EC_GPIO_WAVE], 0); mdelay(50); gpiod_direction_output(ddata->off_gpios->desc[EC_GPIO_WAVE], 1); } } static int kb3930_restart(struct notifier_block *this, unsigned long mode, void *cmd) { kb3930_off(kb3930_power_off, EC_OFF_MODE_REBOOT); return NOTIFY_DONE; } static void kb3930_pm_power_off(void) { kb3930_off(kb3930_power_off, EC_OFF_MODE_POWER); } static struct notifier_block kb3930_restart_nb = { .notifier_call = kb3930_restart, }; static const struct mfd_cell ariel_ec_cells[] = { { .name = "dell-wyse-ariel-led", }, { .name = "dell-wyse-ariel-power", }, }; static int kb3930_ec_ram_reg_write(void *context, unsigned int reg, unsigned int val) { struct kb3930 *ddata = context; return i2c_smbus_write_word_data(ddata->client, EC_RAM_OUT, (val << 8) | reg); } static int kb3930_ec_ram_reg_read(void *context, unsigned int reg, unsigned int *val) { struct kb3930 *ddata = context; int ret; ret = i2c_smbus_write_word_data(ddata->client, EC_RAM_IN, reg); if (ret < 0) return ret; ret = i2c_smbus_read_word_data(ddata->client, EC_DATA_IN); if (ret < 0) return ret; *val = ret >> 8; return 0; } static const struct regmap_config kb3930_ram_regmap_config = { .name = "ec_ram", .reg_bits = 8, .val_bits = 8, .reg_stride = 1, .max_register = 0xff, .reg_write = kb3930_ec_ram_reg_write, .reg_read = kb3930_ec_ram_reg_read, .fast_io = false, }; static int kb3930_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct device_node *np = dev->of_node; struct kb3930 *ddata; unsigned int model; int ret; ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); if (!ddata) return -ENOMEM; kb3930_power_off = ddata; ddata->client = client; i2c_set_clientdata(client, ddata); ddata->ram_regmap = devm_regmap_init(dev, NULL, ddata, &kb3930_ram_regmap_config); if (IS_ERR(ddata->ram_regmap)) return PTR_ERR(ddata->ram_regmap); ret = regmap_read(ddata->ram_regmap, EC_MODEL, &model); if (ret < 0) return ret; /* Currently we only support the cells present on Dell Ariel model. */ if (model != 'J') { dev_err(dev, "unknown board model: %02x\n", model); return -ENODEV; } ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, ariel_ec_cells, ARRAY_SIZE(ariel_ec_cells), NULL, 0, NULL); if (ret) return ret; if (of_property_read_bool(np, "system-power-controller")) { ddata->off_gpios = devm_gpiod_get_array_optional(dev, "off", GPIOD_IN); if (IS_ERR(ddata->off_gpios)) return PTR_ERR(ddata->off_gpios); if (ddata->off_gpios->ndescs < 2) { dev_err(dev, "invalid off-gpios property\n"); return -EINVAL; } } if (ddata->off_gpios) { register_restart_handler(&kb3930_restart_nb); if (!pm_power_off) pm_power_off = kb3930_pm_power_off; } return 0; } static void kb3930_remove(struct i2c_client *client) { struct kb3930 *ddata = i2c_get_clientdata(client); if (ddata->off_gpios) { if (pm_power_off == kb3930_pm_power_off) pm_power_off = NULL; unregister_restart_handler(&kb3930_restart_nb); } kb3930_power_off = NULL; } static const struct of_device_id kb3930_dt_ids[] = { { .compatible = "ene,kb3930" }, { } }; MODULE_DEVICE_TABLE(of, kb3930_dt_ids); static struct i2c_driver kb3930_driver = { .probe_new = kb3930_probe, .remove = kb3930_remove, .driver = { .name = "ene-kb3930", .of_match_table = kb3930_dt_ids, }, }; module_i2c_driver(kb3930_driver); MODULE_AUTHOR("Lubomir Rintel "); MODULE_DESCRIPTION("ENE KB3930 Embedded Controller Driver"); MODULE_LICENSE("Dual BSD/GPL");