// SPDX-License-Identifier: GPL-2.0+ #include #include #include #include #include #include #include #include #include /* Register Addresses (B=byte; W=word; S=string) */ #define REGB_STATUS 0x02 #define REGW_VOLTAGE_NOW 0x20 #define REGW_CURRENT_NOW 0x24 #define REGW_CAPACITY 0x28 #define REGW_CHARGE_NOW 0x2a #define REGW_CHARGE_FULL 0x2c #define REGW_CYCLE_COUNT 0x3a #define REGW_CHARGE_FULL_DESIGN 0x3c #define REGW_VOLTAGE_MAX_DESIGN 0x3e #define REGW_SERIAL_NUMBER 0x44 #define REGS_MANUFACTURER 0x46 #define REGS_MODEL_NAME 0x52 #define REGS_TECHNOLOGY 0x5a #define REGB_ONLINE 0x67 struct srt_ec_device { struct i2c_client *client; struct power_supply *bat; struct power_supply *psy; struct gpio_desc *enable_gpiod; struct delayed_work poll_work; unsigned int technology; unsigned int capacity; const char *serial; char manufacturer[13]; char model_name[10]; }; static const enum power_supply_property srt_bat_power_supply_props[] = { POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CYCLE_COUNT, POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_SERIAL_NUMBER, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, }; static const enum power_supply_property srt_psy_power_supply_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_PRESENT, }; static int srt_bat_get_value(struct i2c_client *client, int reg, int *val) { int ret; switch (reg) { case REGW_CHARGE_NOW: case REGW_CHARGE_FULL_DESIGN: case REGW_CHARGE_FULL: case REGW_VOLTAGE_MAX_DESIGN: case REGW_VOLTAGE_NOW: ret = i2c_smbus_read_word_data(client, reg); if (ret < 0) return ret; *val = ret * 1000; break; case REGW_CURRENT_NOW: ret = i2c_smbus_read_word_data(client, reg); if (ret < 0) return ret; *val = (s16)ret * 1000; break; case REGW_CAPACITY: case REGW_CYCLE_COUNT: ret = i2c_smbus_read_word_data(client, reg); if (ret < 0) return ret; *val = ret; break; case REGB_STATUS: ret = i2c_smbus_read_byte_data(client, reg); if (ret < 0) return ret; if (ret & BIT(0)) *val = POWER_SUPPLY_STATUS_CHARGING; else *val = POWER_SUPPLY_STATUS_DISCHARGING; break; case REGB_ONLINE: ret = i2c_smbus_read_byte_data(client, reg); if (ret < 0) return ret; *val = (ret & BIT(1)) >> 1; break; default: return -EINVAL; } return 0; } static int srt_bat_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct srt_ec_device *srt = power_supply_get_drvdata(psy); struct i2c_client *client = srt->client; int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_MANUFACTURER: val->strval = srt->manufacturer; break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = srt->model_name; break; case POWER_SUPPLY_PROP_SERIAL_NUMBER: val->strval = srt->serial; break; case POWER_SUPPLY_PROP_CAPACITY: ret = srt_bat_get_value(client, REGW_CAPACITY, &val->intval); break; case POWER_SUPPLY_PROP_CHARGE_NOW: ret = srt_bat_get_value(client, REGW_CHARGE_NOW, &val->intval); break; case POWER_SUPPLY_PROP_CHARGE_FULL: ret = srt_bat_get_value(client, REGW_CHARGE_FULL, &val->intval); break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ret = srt_bat_get_value(client, REGW_CHARGE_FULL_DESIGN, &val->intval); break; case POWER_SUPPLY_PROP_CURRENT_NOW: ret = srt_bat_get_value(client, REGW_CURRENT_NOW, &val->intval); break; case POWER_SUPPLY_PROP_CYCLE_COUNT: ret = srt_bat_get_value(client, REGW_CYCLE_COUNT, &val->intval); break; case POWER_SUPPLY_PROP_PRESENT: val->intval = 1; break; case POWER_SUPPLY_PROP_ONLINE: ret = srt_bat_get_value(client, REGB_ONLINE, &val->intval); break; case POWER_SUPPLY_PROP_STATUS: if (srt->capacity < 100) ret = srt_bat_get_value(client, REGB_STATUS, &val->intval); else val->intval = POWER_SUPPLY_STATUS_FULL; break; case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = srt->technology; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: ret = srt_bat_get_value(client, REGW_VOLTAGE_MAX_DESIGN, &val->intval); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: ret = srt_bat_get_value(client, REGW_VOLTAGE_NOW, &val->intval); break; default: return -EINVAL; } return ret; } static int srt_psy_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct srt_ec_device *srt = power_supply_get_drvdata(psy); struct i2c_client *client = srt->client; int ret; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: case POWER_SUPPLY_PROP_PRESENT: ret = i2c_smbus_read_byte_data(client, REGB_ONLINE); if (ret < 0) return ret; val->intval = ret & BIT(0); break; default: return -EINVAL; } return 0; } static void srt_bat_poll_work(struct work_struct *work) { struct srt_ec_device *srt = container_of(work, struct srt_ec_device, poll_work.work); int ret, capacity; ret = srt_bat_get_value(srt->client, REGW_CAPACITY, &capacity); if (!ret && capacity != srt->capacity) { srt->capacity = capacity; power_supply_changed(srt->bat); } /* continuously send uevent notification */ schedule_delayed_work(&srt->poll_work, 30 * HZ); } static irqreturn_t srt_psy_detect_irq(int irq, void *dev_id) { struct srt_ec_device *srt = dev_id; power_supply_changed(srt->psy); return IRQ_HANDLED; } static const struct power_supply_desc srt_bat_power_supply_desc = { .name = "surface-rt-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = srt_bat_power_supply_props, .num_properties = ARRAY_SIZE(srt_bat_power_supply_props), .get_property = srt_bat_power_supply_get_property, .external_power_changed = power_supply_changed, }; static const struct power_supply_desc srt_psy_power_supply_desc = { .name = "surface-rt-ac-adapter", .type = POWER_SUPPLY_TYPE_MAINS, .properties = srt_psy_power_supply_props, .num_properties = ARRAY_SIZE(srt_psy_power_supply_props), .get_property = srt_psy_power_supply_get_property, }; static char *battery_supplied_to[] = { "surface-rt-battery" }; static int srt_ec_probe(struct i2c_client *client) { struct power_supply_config bat_cfg = {}; struct power_supply_config psy_cfg = {}; struct device *dev = &client->dev; struct srt_ec_device *srt; char str_buf[4]; int ret; srt = devm_kzalloc(dev, sizeof(*srt), GFP_KERNEL); if (!srt) return -ENOMEM; i2c_set_clientdata(client, srt); srt->client = client; srt->enable_gpiod = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH); if (IS_ERR(srt->enable_gpiod)) return dev_err_probe(dev, PTR_ERR(srt->enable_gpiod), "failed to get enable gpio\n"); /* wait till EC is ready */ usleep_range(1000, 1500); ret = i2c_smbus_read_word_data(client, REGW_SERIAL_NUMBER); if (ret < 0) return ret; srt->serial = devm_kasprintf(dev, GFP_KERNEL, "%04x", ret); if (!srt->serial) return -ENOMEM; ret = i2c_smbus_read_i2c_block_data(client, REGS_MANUFACTURER, sizeof(srt->manufacturer) - 1, srt->manufacturer); if (ret < 0) return ret; ret = i2c_smbus_read_i2c_block_data(client, REGS_MODEL_NAME, sizeof(srt->model_name) - 1, srt->model_name); if (ret < 0) return ret; ret = i2c_smbus_read_i2c_block_data(client, REGS_TECHNOLOGY, sizeof(str_buf), str_buf); if (ret < 0) return ret; if (!strncmp(str_buf, "LION", 4)) srt->technology = POWER_SUPPLY_TECHNOLOGY_LION; else srt->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; bat_cfg.drv_data = srt; bat_cfg.fwnode = dev_fwnode(dev); srt->bat = devm_power_supply_register(dev, &srt_bat_power_supply_desc, &bat_cfg); if (IS_ERR(srt->bat)) return dev_err_probe(dev, PTR_ERR(srt->bat), "failed to register battery power supply\n"); psy_cfg.drv_data = srt; psy_cfg.fwnode = dev_fwnode(dev); psy_cfg.supplied_to = battery_supplied_to; psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); srt->psy = devm_power_supply_register(dev, &srt_psy_power_supply_desc, &psy_cfg); if (IS_ERR(srt->psy)) return dev_err_probe(dev, PTR_ERR(srt->psy), "failed to register AC power supply\n"); ret = devm_request_threaded_irq(dev, client->irq, NULL, srt_psy_detect_irq, IRQF_ONESHOT, client->name, srt); if (ret < 0) return dev_err_probe(dev, ret, "failed to request interrupt\n"); ret = devm_delayed_work_autocancel(dev, &srt->poll_work, srt_bat_poll_work); if (ret < 0) return ret; schedule_delayed_work(&srt->poll_work, HZ); return 0; } static int srt_ec_suspend(struct device *dev) { struct srt_ec_device *srt = dev_get_drvdata(dev); cancel_delayed_work_sync(&srt->poll_work); return 0; } static int srt_ec_resume(struct device *dev) { struct srt_ec_device *srt = dev_get_drvdata(dev); schedule_delayed_work(&srt->poll_work, HZ); return 0; } static DEFINE_SIMPLE_DEV_PM_OPS(srt_ec_pm_ops, srt_ec_suspend, srt_ec_resume); static const struct of_device_id srt_ec_of_match[] = { { .compatible = "microsoft,surface-rt-ec" }, { } }; MODULE_DEVICE_TABLE(of, srt_ec_of_match); static struct i2c_driver srt_ec_driver = { .driver = { .name = "surface-rt-ec", .of_match_table = srt_ec_of_match, .pm = &srt_ec_pm_ops, }, .probe = srt_ec_probe, }; module_i2c_driver(srt_ec_driver); MODULE_AUTHOR("Jonas Schwöbel "); MODULE_DESCRIPTION("Surface RT Embedded Controller driver"); MODULE_LICENSE("GPL");