ene-kb3930.c (4895B)
1// SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0-or-later 2/* 3 * ENE KB3930 Embedded Controller Driver 4 * 5 * Copyright (C) 2020 Lubomir Rintel 6 */ 7 8#include <linux/delay.h> 9#include <linux/gpio/consumer.h> 10#include <linux/i2c.h> 11#include <linux/mfd/core.h> 12#include <linux/module.h> 13#include <linux/reboot.h> 14#include <linux/regmap.h> 15 16/* I2C registers that are multiplexing access to the EC RAM. */ 17enum { 18 EC_DATA_IN = 0x00, 19 EC_RAM_OUT = 0x80, 20 EC_RAM_IN = 0x81, 21}; 22 23/* EC RAM registers. */ 24enum { 25 EC_MODEL = 0x30, 26 EC_VERSION_MAJ = 0x31, 27 EC_VERSION_MIN = 0x32, 28}; 29 30struct kb3930 { 31 struct i2c_client *client; 32 struct regmap *ram_regmap; 33 struct gpio_descs *off_gpios; 34}; 35 36static struct kb3930 *kb3930_power_off; 37 38#define EC_GPIO_WAVE 0 39#define EC_GPIO_OFF_MODE 1 40 41#define EC_OFF_MODE_REBOOT 0 42#define EC_OFF_MODE_POWER 1 43 44static void kb3930_off(struct kb3930 *ddata, int off_mode) 45{ 46 gpiod_direction_output(ddata->off_gpios->desc[EC_GPIO_OFF_MODE], 47 off_mode); 48 49 /* 50 * This creates a 10 Hz wave on EC_GPIO_WAVE that signals a 51 * shutdown request to the EC. Once the EC detects it, it will 52 * proceed to turn the power off or reset the board depending on 53 * the value of EC_GPIO_OFF_MODE. 54 */ 55 while (1) { 56 mdelay(50); 57 gpiod_direction_output(ddata->off_gpios->desc[EC_GPIO_WAVE], 0); 58 mdelay(50); 59 gpiod_direction_output(ddata->off_gpios->desc[EC_GPIO_WAVE], 1); 60 } 61} 62 63static int kb3930_restart(struct notifier_block *this, 64 unsigned long mode, void *cmd) 65{ 66 kb3930_off(kb3930_power_off, EC_OFF_MODE_REBOOT); 67 return NOTIFY_DONE; 68} 69 70static void kb3930_pm_power_off(void) 71{ 72 kb3930_off(kb3930_power_off, EC_OFF_MODE_POWER); 73} 74 75static struct notifier_block kb3930_restart_nb = { 76 .notifier_call = kb3930_restart, 77}; 78 79static const struct mfd_cell ariel_ec_cells[] = { 80 { .name = "dell-wyse-ariel-led", }, 81 { .name = "dell-wyse-ariel-power", }, 82}; 83 84static int kb3930_ec_ram_reg_write(void *context, unsigned int reg, 85 unsigned int val) 86{ 87 struct kb3930 *ddata = context; 88 89 return i2c_smbus_write_word_data(ddata->client, EC_RAM_OUT, 90 (val << 8) | reg); 91} 92 93static int kb3930_ec_ram_reg_read(void *context, unsigned int reg, 94 unsigned int *val) 95{ 96 struct kb3930 *ddata = context; 97 int ret; 98 99 ret = i2c_smbus_write_word_data(ddata->client, EC_RAM_IN, reg); 100 if (ret < 0) 101 return ret; 102 103 ret = i2c_smbus_read_word_data(ddata->client, EC_DATA_IN); 104 if (ret < 0) 105 return ret; 106 107 *val = ret >> 8; 108 return 0; 109} 110 111static const struct regmap_config kb3930_ram_regmap_config = { 112 .name = "ec_ram", 113 .reg_bits = 8, 114 .val_bits = 8, 115 .reg_stride = 1, 116 .max_register = 0xff, 117 .reg_write = kb3930_ec_ram_reg_write, 118 .reg_read = kb3930_ec_ram_reg_read, 119 .fast_io = false, 120}; 121 122static int kb3930_probe(struct i2c_client *client) 123{ 124 struct device *dev = &client->dev; 125 struct device_node *np = dev->of_node; 126 struct kb3930 *ddata; 127 unsigned int model; 128 int ret; 129 130 ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); 131 if (!ddata) 132 return -ENOMEM; 133 134 kb3930_power_off = ddata; 135 ddata->client = client; 136 i2c_set_clientdata(client, ddata); 137 138 ddata->ram_regmap = devm_regmap_init(dev, NULL, ddata, 139 &kb3930_ram_regmap_config); 140 if (IS_ERR(ddata->ram_regmap)) 141 return PTR_ERR(ddata->ram_regmap); 142 143 ret = regmap_read(ddata->ram_regmap, EC_MODEL, &model); 144 if (ret < 0) 145 return ret; 146 147 /* Currently we only support the cells present on Dell Ariel model. */ 148 if (model != 'J') { 149 dev_err(dev, "unknown board model: %02x\n", model); 150 return -ENODEV; 151 } 152 153 ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, 154 ariel_ec_cells, 155 ARRAY_SIZE(ariel_ec_cells), 156 NULL, 0, NULL); 157 if (ret) 158 return ret; 159 160 if (of_property_read_bool(np, "system-power-controller")) { 161 ddata->off_gpios = 162 devm_gpiod_get_array_optional(dev, "off", GPIOD_IN); 163 if (IS_ERR(ddata->off_gpios)) 164 return PTR_ERR(ddata->off_gpios); 165 if (ddata->off_gpios->ndescs < 2) { 166 dev_err(dev, "invalid off-gpios property\n"); 167 return -EINVAL; 168 } 169 } 170 171 if (ddata->off_gpios) { 172 register_restart_handler(&kb3930_restart_nb); 173 if (!pm_power_off) 174 pm_power_off = kb3930_pm_power_off; 175 } 176 177 return 0; 178} 179 180static int kb3930_remove(struct i2c_client *client) 181{ 182 struct kb3930 *ddata = i2c_get_clientdata(client); 183 184 if (ddata->off_gpios) { 185 if (pm_power_off == kb3930_pm_power_off) 186 pm_power_off = NULL; 187 unregister_restart_handler(&kb3930_restart_nb); 188 } 189 kb3930_power_off = NULL; 190 191 return 0; 192} 193 194static const struct of_device_id kb3930_dt_ids[] = { 195 { .compatible = "ene,kb3930" }, 196 { } 197}; 198MODULE_DEVICE_TABLE(of, kb3930_dt_ids); 199 200static struct i2c_driver kb3930_driver = { 201 .probe_new = kb3930_probe, 202 .remove = kb3930_remove, 203 .driver = { 204 .name = "ene-kb3930", 205 .of_match_table = kb3930_dt_ids, 206 }, 207}; 208module_i2c_driver(kb3930_driver); 209 210MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); 211MODULE_DESCRIPTION("ENE KB3930 Embedded Controller Driver"); 212MODULE_LICENSE("Dual BSD/GPL");