From 87b29460ab4f823147d6ce732e6f57552a53b2cc Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 25 Oct 2013 21:06:08 +0400 Subject: [PATCH] staging: Add a50x EC compliant drivers Signed-off-by: Dmitry Osipenko --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 2 + drivers/staging/a500/Kconfig | 12 + drivers/staging/a500/Makefile | 1 + drivers/staging/a500/ec/Kconfig | 25 + drivers/staging/a500/ec/Makefile | 4 + drivers/staging/a500/ec/a500_ec.c | 217 +++++++++ drivers/staging/a500/ec/a500_ec_battery.c | 426 +++++++++++++++++ drivers/staging/a500/ec/a500_ec_leds.c | 119 +++++ drivers/staging/a500/ec/a500_legacy_sysfs.c | 493 ++++++++++++++++++++ drivers/staging/a500/ec/ec.h | 32 ++ 11 files changed, 1333 insertions(+) create mode 100644 drivers/staging/a500/Kconfig create mode 100644 drivers/staging/a500/Makefile create mode 100644 drivers/staging/a500/ec/Kconfig create mode 100644 drivers/staging/a500/ec/Makefile create mode 100644 drivers/staging/a500/ec/a500_ec.c create mode 100644 drivers/staging/a500/ec/a500_ec_battery.c create mode 100644 drivers/staging/a500/ec/a500_ec_leds.c create mode 100644 drivers/staging/a500/ec/a500_legacy_sysfs.c create mode 100644 drivers/staging/a500/ec/ec.h diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 7c197d1a1231..46e38e702dd8 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -24,6 +24,8 @@ menuconfig STAGING if STAGING +source "drivers/staging/a500/Kconfig" + source "drivers/staging/slicoss/Kconfig" source "drivers/staging/wlan-ng/Kconfig" diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index a470c7276142..4656175ffce2 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -1,5 +1,7 @@ # Makefile for staging directory +obj-$(CONFIG_A500) += a500/ + obj-y += media/ obj-$(CONFIG_SLICOSS) += slicoss/ obj-$(CONFIG_PRISM2_USB) += wlan-ng/ diff --git a/drivers/staging/a500/Kconfig b/drivers/staging/a500/Kconfig new file mode 100644 index 000000000000..859ee0df47a9 --- /dev/null +++ b/drivers/staging/a500/Kconfig @@ -0,0 +1,12 @@ +config A500 + bool "Acer A500 drivers" + depends on ARCH_TEGRA + default n + ---help--- + Say Y here to build Acer A500 tablet device drivers. + +if A500 + +source "drivers/staging/a500/ec/Kconfig" + +endif # A500 diff --git a/drivers/staging/a500/Makefile b/drivers/staging/a500/Makefile new file mode 100644 index 000000000000..e4dfc34989a6 --- /dev/null +++ b/drivers/staging/a500/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_A500) += ec/ diff --git a/drivers/staging/a500/ec/Kconfig b/drivers/staging/a500/ec/Kconfig new file mode 100644 index 000000000000..e20477516130 --- /dev/null +++ b/drivers/staging/a500/ec/Kconfig @@ -0,0 +1,25 @@ +config MFD_EC_A500 + bool "Embedded Controller driver for Acer A50x tablets" + depends on I2C && ARCH_TEGRA + select MFD_CORE + help + Say Y to include support for Acer A50x compliant embedded + controller. + +config EC_A500_LEGACY_SYSFS + tristate "Legacy A50x EC sysfs for Android compatibility" + depends on MFD_EC_A500 + help + Say Y to include support for Acer A50x EC legacy sysfs. + +config EC_A500_BATTERY + tristate "Acer A50x battery driver" + depends on MFD_EC_A500 && POWER_SUPPLY + help + Say Y to include support for battery behind A500 EC. + +config EC_A500_LEDS + tristate "LEDs support for Acer A50x" + depends on MFD_EC_A500 && LEDS_CLASS + help + Say Y to enable control of power button leds. diff --git a/drivers/staging/a500/ec/Makefile b/drivers/staging/a500/ec/Makefile new file mode 100644 index 000000000000..77a86477592f --- /dev/null +++ b/drivers/staging/a500/ec/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_EC_A500_BATTERY) += a500_ec_battery.o +obj-$(CONFIG_EC_A500_LEDS) += a500_ec_leds.o +obj-$(CONFIG_EC_A500_LEGACY_SYSFS) += a500_legacy_sysfs.o +obj-$(CONFIG_MFD_EC_A500) += a500_ec.o diff --git a/drivers/staging/a500/ec/a500_ec.c b/drivers/staging/a500/ec/a500_ec.c new file mode 100644 index 000000000000..ccaadc99d7c5 --- /dev/null +++ b/drivers/staging/a500/ec/a500_ec.c @@ -0,0 +1,217 @@ +/* + * MFD driver for Acer A50x embedded controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include + +#include + +#include "ec.h" + +/* addr timeout */ +EC_REG_DATA(SHUTDOWN, 0x52, 0); +EC_REG_DATA(WARM_REBOOT, 0x54, 0); +EC_REG_DATA(COLD_REBOOT, 0x55, 1000); + +static DEFINE_MUTEX(ec_mutex); + +static struct ec_info { + struct i2c_client *client; + struct notifier_block panic_notifier; + int i2c_retry_count; +} *ec_chip; + +inline void ec_lock(void) +{ + mutex_lock(&ec_mutex); +} +EXPORT_SYMBOL_GPL(ec_lock); + +inline void ec_unlock(void) +{ + mutex_unlock(&ec_mutex); +} +EXPORT_SYMBOL_GPL(ec_unlock); + +#define I2C_ERR_TIMEOUT 500 +int ec_read_word_data_locked(struct ec_reg_data *reg_data) +{ + struct i2c_client *client = ec_chip->client; + int retries = ec_chip->i2c_retry_count; + s32 ret = 0; + + while (retries > 0) { + ret = i2c_smbus_read_word_data(client, reg_data->addr); + if (ret >= 0) + break; + msleep(I2C_ERR_TIMEOUT); + retries--; + } + + if (ret < 0) { + dev_err(&client->dev, "%s: i2c read at address 0x%x failed\n", + __func__, reg_data->addr); + return ret; + } + + msleep(reg_data->timeout); + + return le16_to_cpu(ret); +} +EXPORT_SYMBOL_GPL(ec_read_word_data_locked); + +int ec_read_word_data(struct ec_reg_data *reg_data) +{ + s32 ret; + + ec_lock(); + ret = ec_read_word_data_locked(reg_data); + ec_unlock(); + + return ret; +} +EXPORT_SYMBOL_GPL(ec_read_word_data); + +int ec_write_word_data_locked(struct ec_reg_data *reg_data, u16 value) +{ + struct i2c_client *client = ec_chip->client; + int retries = ec_chip->i2c_retry_count; + s32 ret = 0; + + while (retries > 0) { + ret = i2c_smbus_write_word_data(client, reg_data->addr, + le16_to_cpu(value)); + if (ret >= 0) + break; + msleep(I2C_ERR_TIMEOUT); + retries--; + } + + if (ret < 0) { + dev_err(&client->dev, "%s: i2c write to address 0x%x failed\n", + __func__, reg_data->addr); + return ret; + } + + msleep(reg_data->timeout); + + return 0; +} +EXPORT_SYMBOL_GPL(ec_write_word_data_locked); + +int ec_write_word_data(struct ec_reg_data *reg_data, u16 value) +{ + s32 ret; + + ec_lock(); + ret = ec_write_word_data_locked(reg_data, value); + ec_unlock(); + + return ret; +} +EXPORT_SYMBOL_GPL(ec_write_word_data); + +static void ec_poweroff(void) +{ + dev_info(&ec_chip->client->dev, "poweroff ...\n"); + + ec_write_word_data(SHUTDOWN, 0); +} + +static void ec_reboot(enum reboot_mode mode, const char *cmd) +{ + if (oops_in_progress) + ec_write_word_data_locked(WARM_REBOOT, 0); + + dev_info(&ec_chip->client->dev, "reboot ...\n"); + + ec_write_word_data(COLD_REBOOT, 1); +} + +static struct mfd_cell ec_cell[] = { + { + .name = "a50x-battery", + .of_compatible = "acer,a50x-battery", + .id = 1, + }, + { + .name = "a50x-leds", + .of_compatible = "acer,a50x-leds", + .id = 1, + }, +}; + +static int ec_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *np = client->dev.of_node; + int ret; + + ec_chip = devm_kzalloc(&client->dev, sizeof(*ec_chip), GFP_KERNEL); + if (!ec_chip) + return -ENOMEM; + + if (of_property_read_u32(np, "ec,i2c-retry-count", + &ec_chip->i2c_retry_count)) + ec_chip->i2c_retry_count = 5; + + ec_chip->client = client; + + /* register battery and leds */ + ret = mfd_add_devices(&client->dev, -1, + ec_cell, ARRAY_SIZE(ec_cell), + NULL, 0, NULL); + if (ret) { + dev_err(&client->dev, "Failed to add subdevices\n"); + return ret; + } + + /* set pm functions */ + if (of_property_read_bool(np, "system-power-controller")) { + arm_pm_restart = ec_reboot; + + if (!pm_power_off) + pm_power_off = ec_poweroff; + } + + dev_dbg(&client->dev, "device registered\n"); + + return 0; +} + +static const struct of_device_id ec_match[] = { + { .compatible = "acer,a50x-ec" }, + { } +}; +MODULE_DEVICE_TABLE(of, ec_match); + +static const struct i2c_device_id ec_id[] = { + { "a50x_EC", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ec_id); + +static struct i2c_driver a50x_ec_driver = { + .driver = { + .name = "a50x-ec", + .of_match_table = ec_match, + .owner = THIS_MODULE, + }, + .id_table = ec_id, + .probe = ec_probe, +}; +module_i2c_driver(a50x_ec_driver); + +MODULE_ALIAS("i2c:a50x-ec"); +MODULE_DESCRIPTION("Acer A50x EC MFD driver"); +MODULE_AUTHOR("Dmitry Osipenko "); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/a500/ec/a500_ec_battery.c b/drivers/staging/a500/ec/a500_ec_battery.c new file mode 100644 index 000000000000..f47087553c32 --- /dev/null +++ b/drivers/staging/a500/ec/a500_ec_battery.c @@ -0,0 +1,426 @@ +/* + * Battery driver for Acer A50x tablets + * + * based on nVidia's SBS and Acer's EC battery drivers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include + +#include "ec.h" + +#define BATTERY_NAME "ec-battery" +#define POLL_TIME_DEFAULT 60 + +enum { + REG_VOLTAGE, + REG_CAPACITY, + REG_HEALTH, + REG_DESIGN_CAPACITY, + REG_SERIAL_NUMBER, + REG_TEMPERATURE, + REG_CURRENT, +}; + +#define EC_DATA(_psp, _addr, _timeout) { \ + .psp = _psp, \ + .reg_data = { \ + .addr = _addr, \ + .timeout = _timeout \ + }, \ +} + +static struct chip_data { + enum power_supply_property psp; + struct ec_reg_data reg_data; +} ec_data[] = { + [REG_VOLTAGE] = + EC_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x01, 0), + [REG_CAPACITY] = + EC_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x00, 0), + [REG_CURRENT] = + EC_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x03, 10), + [REG_DESIGN_CAPACITY] = + EC_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x08, 0), + [REG_HEALTH] = + EC_DATA(POWER_SUPPLY_PROP_HEALTH, 0x09, 10), + [REG_SERIAL_NUMBER] = + EC_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x6a, 0), + [REG_TEMPERATURE] = + EC_DATA(POWER_SUPPLY_PROP_TEMP, 0x0A, 0), +}; + +static enum power_supply_property ec_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +struct ec_battery_info { + struct device *dev; + struct delayed_work poll_work; + struct notifier_block panic_notifier; + struct power_supply *bat; + struct power_supply_desc bat_desc; + bool poll_disabled; + bool is_supplied; + bool is_panic; + int capacity; + int poll_interval; +}; + +static int ec_get_battery_presence(union power_supply_propval *val) +{ + s32 ret; + + ret = ec_read_word_data(&ec_data[REG_DESIGN_CAPACITY].reg_data); + if (ret <= 0) + val->intval = 0; + else + val->intval = 1; + + return 0; +} + +static int ec_get_battery_health(union power_supply_propval *val) +{ + s32 ret; + + ret = ec_read_word_data(&ec_data[REG_HEALTH].reg_data); + if (ret < 0) + return ret; + + if (ret > 50) + val->intval = POWER_SUPPLY_HEALTH_GOOD; + else + val->intval = POWER_SUPPLY_HEALTH_DEAD; + + return 0; +} + +static bool ec_get_battery_capacity(struct ec_battery_info *chip) +{ + int capacity; + s32 ret; + + ret = ec_read_word_data(&ec_data[REG_CAPACITY].reg_data); + if (ret < 0) + return false; + + /* sbs spec says that this can be >100 % + * even if max value is 100 % */ + capacity = min(ret, 100); + + if (chip->capacity != capacity) { + chip->capacity = capacity; + return true; + } + + return false; +} + +static void ec_get_battery_status(struct ec_battery_info *chip, + union power_supply_propval *val) +{ + if (chip->capacity < 100) { + if (chip->is_supplied) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } else + val->intval = POWER_SUPPLY_STATUS_FULL; +} + +static int ec_get_battery_property(int reg_offset, + union power_supply_propval *val) +{ + s32 ret; + + ret = ec_read_word_data(&ec_data[reg_offset].reg_data); + if (ret < 0) + return ret; + + val->intval = ret; + + return 0; +} + +static void ec_unit_adjustment(struct device *dev, + enum power_supply_property psp, + union power_supply_propval *val) +{ +#define BASE_UNIT_CONVERSION 1000 +#define TEMP_KELVIN_TO_CELSIUS 2731 + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = (s16) val->intval; + break; + + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval *= BASE_UNIT_CONVERSION; + break; + + case POWER_SUPPLY_PROP_TEMP: + /* sbs provides battery temperature in 0.1K + * so convert it to 0.1°C + */ + val->intval -= TEMP_KELVIN_TO_CELSIUS; + break; + + default: + dev_dbg(dev, + "%s: no need for unit conversion %d\n", __func__, psp); + } +} + +#define SERIAL_PARTS_NB 11 +#define SERIAL_STRLEN (SERIAL_PARTS_NB * 2 + 1) +static char ec_serial[SERIAL_STRLEN] = ""; + +static int ec_get_battery_serial_number(union power_supply_propval *val) +{ + s32 ret; + int i; + + if (strlen(ec_serial) == 0) { + ec_lock(); + for (i = 0; i < SERIAL_PARTS_NB; i++) { + ret = ec_read_word_data_locked( + &ec_data[REG_SERIAL_NUMBER].reg_data); + + snprintf(ec_serial, SERIAL_STRLEN, + "%s%s", ec_serial, (char *)&ret); + } + ec_unlock(); + } + + val->strval = ec_serial; + + return 0; +} + +static int ec_get_property_index(struct device *dev, + enum power_supply_property psp) +{ + int count; + for (count = 0; count < ARRAY_SIZE(ec_data); count++) + if (psp == ec_data[count].psp) + return count; + + dev_warn(dev, "%s: Invalid Property - %d\n", __func__, psp); + + return -EINVAL; +} + +static int ec_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ec_battery_info *chip = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = ec_get_battery_serial_number(val); + break; + + case POWER_SUPPLY_PROP_HEALTH: + ret = ec_get_battery_health(val); + break; + + case POWER_SUPPLY_PROP_PRESENT: + ret = ec_get_battery_presence(val); + break; + + case POWER_SUPPLY_PROP_STATUS: + ec_get_battery_status(chip, val); + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ec_get_battery_capacity(chip); + val->intval = chip->capacity; + break; + + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_TEMP: + ret = ec_get_property_index(chip->dev, psp); + if (ret < 0) + break; + + ret = ec_get_battery_property(ret, val); + break; + + default: + dev_err(chip->dev, "%s: INVALID property\n", __func__); + return -EINVAL; + } + + if (!ret) { + /* Convert units to match requirements for power supply class */ + ec_unit_adjustment(chip->dev, psp, val); + } + + dev_dbg(chip->dev, + "%s: property = %d, value = %x\n", __func__, psp, val->intval); + + /* battery not present, so return NODATA for properties */ + if (ret) + return -ENODATA; + + return 0; +} + +static void ec_external_power_changed(struct power_supply *psy) +{ + struct ec_battery_info *chip = power_supply_get_drvdata(psy); + bool supplied_state; + + supplied_state = power_supply_am_i_supplied(psy); + + /* suppress bogus notifications */ + if (chip->is_supplied != supplied_state) { + chip->is_supplied = supplied_state; + /* notify OS immediately */ + flush_delayed_work(&chip->poll_work); + } +} + +static void ec_delayed_work(struct work_struct *work) +{ + struct ec_battery_info *chip; + bool capacity_changed; + + chip = container_of(work, struct ec_battery_info, poll_work.work); + + if (chip->poll_disabled) + return; + + capacity_changed = ec_get_battery_capacity(chip); + + if (capacity_changed) + power_supply_changed(chip->bat); + + /* send continuous uevent notify */ + set_timer_slack(&chip->poll_work.timer, chip->poll_interval * HZ / 4); + schedule_delayed_work(&chip->poll_work, chip->poll_interval * HZ); +} + +static int ec_probe(struct platform_device *pdev) +{ + struct power_supply_config psy_cfg = {}; + struct ec_battery_info *chip; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = chip; + + chip->dev = &pdev->dev; + chip->poll_interval = POLL_TIME_DEFAULT; + chip->capacity = -1; + + chip->bat_desc.name = BATTERY_NAME; + chip->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY; + chip->bat_desc.properties = ec_properties; + chip->bat_desc.num_properties = ARRAY_SIZE(ec_properties); + chip->bat_desc.get_property = ec_get_property; + chip->bat_desc.external_power_changed = ec_external_power_changed; + + chip->bat = power_supply_register_no_ws(&pdev->dev, + &chip->bat_desc, &psy_cfg); + if (IS_ERR(chip->bat)) { + dev_err(&pdev->dev, + "%s: Failed to register power supply\n", __func__); + return PTR_ERR(chip->bat); + } + + if (power_supply_am_i_supplied(chip->bat)) + chip->is_supplied = true; + + INIT_DELAYED_WORK(&chip->poll_work, ec_delayed_work); + schedule_work(&chip->poll_work.work); + + platform_set_drvdata(pdev, chip); + + return 0; +} + +static int ec_remove(struct platform_device *pdev) +{ + struct ec_battery_info *chip = dev_get_drvdata(&pdev->dev); + + chip->poll_disabled = true; + cancel_delayed_work_sync(&chip->poll_work); + + power_supply_unregister(chip->bat); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ec_suspend(struct device *dev) +{ + struct ec_battery_info *chip = dev_get_drvdata(dev); + + chip->poll_disabled = true; + cancel_delayed_work_sync(&chip->poll_work); + + return 0; +} + +static int ec_resume(struct device *dev) +{ + struct ec_battery_info *chip = dev_get_drvdata(dev); + + chip->poll_disabled = false; + schedule_delayed_work(&chip->poll_work, HZ); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(ec_battery_pm_ops, ec_suspend, ec_resume); + +static const struct of_device_id ec_battery_match[] = { + { .compatible = "acer,a50x-battery" }, + { } +}; +MODULE_DEVICE_TABLE(of, ec_battery_match); + +static struct platform_driver ec_battery_driver = { + .driver = { + .name = "a50x-battery", + .of_match_table = ec_battery_match, + .owner = THIS_MODULE, + .pm = &ec_battery_pm_ops, + }, + .probe = ec_probe, + .remove = ec_remove, +}; +module_platform_driver(ec_battery_driver); + +MODULE_DESCRIPTION("Acer A50x EC battery driver"); +MODULE_AUTHOR("Dmitry Osipenko "); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/a500/ec/a500_ec_leds.c b/drivers/staging/a500/ec/a500_ec_leds.c new file mode 100644 index 000000000000..8e2b60de1834 --- /dev/null +++ b/drivers/staging/a500/ec/a500_ec_leds.c @@ -0,0 +1,119 @@ +/* + * LEDs driver for Acer A50x tablets + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include + +#include "ec.h" + +struct ec_led { + struct led_classdev cdev; + struct work_struct work; + u8 new_state; +}; + +EC_REG_DATA(RESET_LEDS, 0x40, 100); +EC_REG_DATA(POWER_LED_ON, 0x42, 100); +EC_REG_DATA(CHARGE_LED_ON, 0x43, 100); +EC_REG_DATA(ANDROID_LEDS_OFF, 0x5A, 100); + +static void ec_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct ec_led *led = container_of(led_cdev, struct ec_led, cdev); + + led->new_state = value; + schedule_work(&led->work); +} + +#define EC_LED(_color, _addr) \ +static struct ec_led ec_##_color##_led = { \ + .cdev = { \ + .name = "power-button-" #_color, \ + .brightness_set = ec_led_set, \ + .max_brightness = 1, \ + .flags = LED_CORE_SUSPENDRESUME, \ + }, \ +}; \ +static void ec_##_color##_led_work(struct work_struct *work) \ +{ \ + struct ec_led *led = container_of(work, struct ec_led, work); \ + \ + if (led->new_state) \ + ec_write_word_data(_addr, 0); \ + else \ + ec_write_word_data(RESET_LEDS, 0); \ +} + +EC_LED(white, POWER_LED_ON); +EC_LED(orange, CHARGE_LED_ON); + +static int ec_leds_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int ret; + + INIT_WORK(&ec_white_led.work, ec_white_led_work); + INIT_WORK(&ec_orange_led.work, ec_orange_led_work); + + ret = led_classdev_register(&pdev->dev, &ec_white_led.cdev); + if (ret) { + dev_err(&pdev->dev, + "%s: Failed to register white led\n", __func__); + return ret; + } + + ret = led_classdev_register(&pdev->dev, &ec_orange_led.cdev); + if (ret) { + dev_err(&pdev->dev, + "%s: Failed to register orange led\n", __func__); + led_classdev_unregister(&ec_white_led.cdev); + return ret; + } + + if (of_property_read_bool(np, "leds-reset")) { + ec_write_word_data(RESET_LEDS, 0); + ec_write_word_data(ANDROID_LEDS_OFF, 0); + } + + return 0; +} + +static int ec_leds_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&ec_white_led.cdev); + cancel_work_sync(&ec_white_led.work); + + led_classdev_unregister(&ec_orange_led.cdev); + cancel_work_sync(&ec_orange_led.work); + + return 0; +} + +static const struct of_device_id ec_leds_match[] = { + { .compatible = "acer,a50x-leds" }, + { } +}; +MODULE_DEVICE_TABLE(of, ec_leds_match); + +static struct platform_driver ec_leds_driver = { + .driver = { + .name = "a50x-leds", + .of_match_table = ec_leds_match, + .owner = THIS_MODULE, + }, + .probe = ec_leds_probe, + .remove = ec_leds_remove, +}; +module_platform_driver(ec_leds_driver); + +MODULE_DESCRIPTION("Acer A50x EC LEDs driver"); +MODULE_AUTHOR("Dmitry Osipenko "); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/a500/ec/a500_legacy_sysfs.c b/drivers/staging/a500/ec/a500_legacy_sysfs.c new file mode 100644 index 000000000000..51f710db4feb --- /dev/null +++ b/drivers/staging/a500/ec/a500_legacy_sysfs.c @@ -0,0 +1,493 @@ +/* + * Legacy sysfs for Android compatibility + * + * based on Acer EC battery driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include + +#include "ec.h" + +#define BTMAC_PARTS_NB 3 +#define CABC_MASK BIT(8) +#define GYRO_GAIN_PARTS_NB 18 +#define PART_SZ 4 +#define SYSCONF_MASK 0x0000FFFF +#define WIFIMAC_PARTS_NB 3 + +#define EC_ATTR(_name, _mode, _show, _store) \ +struct kobj_attribute _name##_attr = __ATTR(_name, _mode, _show, _store) + +/* addr timeout */ +EC_REG_DATA(RESET_LED, 0x40, 100); +EC_REG_DATA(LEDS_OFF, 0x41, 100); +EC_REG_DATA(POWER_LED_ON, 0x42, 100); +EC_REG_DATA(CHARGE_LED_ON, 0x43, 100); +EC_REG_DATA(AUDIO_CTRL, 0x44, 0); +EC_REG_DATA(POWER_CTRL_3G, 0x45, 100); +EC_REG_DATA(GPS_POWER_OFF, 0x47, 0); +EC_REG_DATA(GPS_3G_STATUS_RD, 0x48, 0); +EC_REG_DATA(GPS_3G_STATUS_WR, 0x49, 0); +EC_REG_DATA(GPS_POWER_ON, 0x4A, 0); +EC_REG_DATA(MISC_CTRL_RD, 0x4C, 10); +EC_REG_DATA(MISC_CTRL_WR, 0x4D, 10); +EC_REG_DATA(ANDROID_LEDS_OFF, 0x5A, 100); +EC_REG_DATA(BTMAC_RD, 0x62, 10); +EC_REG_DATA(BTMAC_WR, 0x63, 10); +EC_REG_DATA(WIFIMAC_RD, 0x64, 10); +EC_REG_DATA(WIFIMAC_WR, 0x65, 10); +EC_REG_DATA(LS_GAIN_RD, 0x71, 10); +EC_REG_DATA(LS_GAIN_WR, 0x72, 10); +EC_REG_DATA(GYRO_GAIN_RD, 0x73, 10); +EC_REG_DATA(GYRO_GAIN_WR, 0x74, 10); + +static int power_state_3g; +static int power_state_gps; + +static void ec_read_multipart(char *buf, struct ec_reg_data *reg_data, + int parts_nb) +{ + char *write_offset; + int length, i; + s32 ret; + + length = parts_nb * PART_SZ; + write_offset = buf + length; + + ec_lock(); + for (i = 0; i < parts_nb; i++) { + ret = ec_read_word_data_locked(reg_data); + + write_offset -= PART_SZ; + + snprintf(write_offset, length + 1, "%04x%s", + ret, (i == 0) ? "" : write_offset + PART_SZ); + } + ec_unlock(); +} + +static void ec_write_multipart(const char *buf, struct ec_reg_data *reg_data, + int parts_nb) +{ + char part_buf[PART_SZ + 1]; + int read_offset, val, i; + s32 ret; + + /* don't count trailing "\n" */ + ret = strlen(buf) - 1; + + if (ret != parts_nb * PART_SZ) { + pr_err("%s: length %d is not equal to required %d\n", + __func__, ret, parts_nb * PART_SZ); + return; + } + + ec_lock(); + for (i = 0; i < parts_nb; i++) { + read_offset = (parts_nb - i + 1) * PART_SZ; + + snprintf(part_buf, ARRAY_SIZE(part_buf), + "%s", buf + read_offset); + + ret = kstrtoint(part_buf, 16, &val); + if (ret < 0) + pr_err("%s: failed to convert hex str: %s\n", + __func__, part_buf); + + ec_write_word_data_locked(reg_data, val); + } + ec_unlock(); +} + +static ssize_t gyro_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + ec_read_multipart(buf, GYRO_GAIN_RD, GYRO_GAIN_PARTS_NB); + + return sprintf(buf, "%s\n", buf); +} + +static ssize_t gyro_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + ec_write_multipart(buf, GYRO_GAIN_WR, GYRO_GAIN_PARTS_NB); + + return n; +} + +static ssize_t pwr_led_on_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + ec_write_word_data(POWER_LED_ON, 0); + + return n; +} + +static ssize_t chrg_led_on_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + ec_write_word_data(CHARGE_LED_ON, 0); + + return n; +} + +static ssize_t reset_led_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + ec_write_word_data(RESET_LED, 0); + + return n; +} + +static ssize_t leds_off_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + ec_write_word_data(LEDS_OFF, 0); + + return n; +} + +static ssize_t android_off_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + ec_write_word_data(ANDROID_LEDS_OFF, 0); + + return n; +} + +static ssize_t ls_gain_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + s32 ret = ec_read_word_data(LS_GAIN_RD); + + return sprintf(buf, "%04x\n", ret); +} + +static ssize_t ls_gain_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int val; + s32 ret; + + ret = kstrtoint(buf, 16, &val); + if (ret < 0) { + pr_err("%s: failed to convert hex str: %s\n", __func__, buf); + return ret; + } + + ec_write_word_data(LS_GAIN_WR, val); + + return n; +} + +static ssize_t btmac_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + ec_read_multipart(buf, BTMAC_RD, BTMAC_PARTS_NB); + + return sprintf(buf, "%c%c:%c%c:%c%c:%c%c:%c%c:%c%c\n", + buf[0], buf[1], buf[2], buf[3], buf[4], + buf[5], buf[6], buf[7], buf[8], buf[9], + buf[10], buf[11]); +} + +static ssize_t btmac_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + ec_write_multipart(buf, BTMAC_WR, BTMAC_PARTS_NB); + + return n; +} + +static ssize_t wifimac_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ec_read_multipart(buf, WIFIMAC_RD, WIFIMAC_PARTS_NB); + + return sprintf(buf, "%s\n", buf); +} + +static ssize_t wifimac_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + ec_write_multipart(buf, WIFIMAC_WR, WIFIMAC_PARTS_NB); + + return n; +} + +static ssize_t device_status_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + s32 ret; + int i; + + ret = ec_read_word_data(GPS_3G_STATUS_RD); + + for (i = 15; i >= 0; i--) + buf[i] = ret >> (15 - i) & 0x1 ? '1' : '0'; + + return sprintf(buf, "%s\n", buf); +} + +static ssize_t device_status_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + s32 ret; + int val; + + ret = kstrtoint(buf, 10, &val); + if (ret < 0) { + pr_err("%s: failed to convert str: %s\n", __func__, buf); + return ret; + } + + ec_write_word_data(GPS_3G_STATUS_WR, val); + + return n; +} + +static ssize_t status_3g_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", power_state_3g); +} + +static ssize_t status_3g_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + s32 ret; + int val; + + ret = kstrtoint(buf, 10, &val); + if (ret < 0) { + pr_err("%s: failed to convert str: %s\n", __func__, buf); + return ret; + } + + power_state_3g = val; + ec_write_word_data(POWER_CTRL_3G, val); + + return n; +} + +static ssize_t status_gps_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", power_state_gps); +} + +static ssize_t status_gps_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + s32 ret; + int val; + + ret = kstrtoint(buf, 10, &val); + if (ret < 0) { + pr_err("%s: failed to convert str: %s\n", __func__, buf); + return ret; + } + + power_state_gps = !!val; + + if (power_state_gps) + ec_write_word_data(GPS_POWER_ON, 0); + else + ec_write_word_data(GPS_POWER_OFF, 0); + + return n; +} + +static ssize_t cabc_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + bool enabled = ec_read_word_data(MISC_CTRL_RD) & CABC_MASK; + + return sprintf(buf, "%d\n", enabled); +} + +static ssize_t cabc_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + s32 ret; + int val; + + ret = kstrtoint(buf, 10, &val); + if (ret < 0) { + pr_err("%s: failed to convert str: %s\n", __func__, buf); + return ret; + } + + ret = ec_read_word_data(MISC_CTRL_RD); + if (ret < 0) + return ret; + + if (val) + ret |= CABC_MASK; + else + ret &= (~CABC_MASK); + + ec_write_word_data(MISC_CTRL_WR, ret); + + return n; +} + +static ssize_t sysconf_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + s32 ret = ec_read_word_data(MISC_CTRL_RD) & SYSCONF_MASK; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t sysconf_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + s32 ret; + int val; + + ret = kstrtoint(buf, 10, &val); + if (ret < 0) { + pr_err("%s: failed to convert str: %s\n", __func__, buf); + return ret; + } + val &= SYSCONF_MASK; + + ec_write_word_data(MISC_CTRL_WR, val); + + return n; +} + +static ssize_t audioconf_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + s32 ret = ec_read_word_data(AUDIO_CTRL) & SYSCONF_MASK; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t audioconf_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + s32 ret; + int val; + + ret = kstrtoint(buf, 10, &val); + if (ret < 0) { + pr_err("%s: failed to convert str: %s\n", __func__, buf); + return ret; + } + val &= SYSCONF_MASK; + + ec_write_word_data(AUDIO_CTRL, val); + + return n; +} + +static EC_ATTR(GyroGain, + S_IWUSR|S_IRUGO, gyro_show, gyro_store); +static EC_ATTR(PowerLED, + S_IRUGO, NULL, pwr_led_on_store); +static EC_ATTR(ChargeLED, + S_IRUGO, NULL, chrg_led_on_store); +static EC_ATTR(OriSts, + S_IRUGO, NULL, reset_led_store); +static EC_ATTR(OffLED, + S_IRUGO, NULL, leds_off_store); +static EC_ATTR(LEDAndroidOff, + S_IRUGO, NULL, android_off_store); +static EC_ATTR(AutoLSGain, + S_IWUSR|S_IRUGO, ls_gain_show, ls_gain_store); +static EC_ATTR(BTMAC, + S_IWUSR|S_IRUGO, btmac_show, btmac_store); +static EC_ATTR(WIFIMAC, + S_IWUSR|S_IRUGO, wifimac_show, wifimac_store); +static EC_ATTR(DeviceStatus, + S_IWUSR|S_IRUGO, device_status_show, device_status_store); +static EC_ATTR(ThreeGPower, + S_IWUSR|S_IRUGO, status_3g_show, status_3g_store); +static EC_ATTR(GPSPower, + S_IWUSR|S_IRUGO, status_gps_show, status_gps_store); +static EC_ATTR(Cabc, + S_IWUSR|S_IRUGO, cabc_show, cabc_store); +static EC_ATTR(SystemConfig, + S_IWUSR|S_IRUGO, sysconf_show, sysconf_store); +static EC_ATTR(MicSwitch, + S_IWUSR|S_IRUGO, audioconf_show, audioconf_store); + +static struct attribute *ec_attrs[] = { + &GyroGain_attr.attr, + &PowerLED_attr.attr, + &ChargeLED_attr.attr, + &OriSts_attr.attr, + &OffLED_attr.attr, + &LEDAndroidOff_attr.attr, + &AutoLSGain_attr.attr, + &BTMAC_attr.attr, + &WIFIMAC_attr.attr, + &DeviceStatus_attr.attr, + &ThreeGPower_attr.attr, + &GPSPower_attr.attr, + &Cabc_attr.attr, + &SystemConfig_attr.attr, + &MicSwitch_attr.attr, + NULL, +}; + +static struct attribute_group ec_attr_group = { + .attrs = ec_attrs, +}; + +static struct kobject *ec_legacy_kobj; + +int ec_create_legacy_sysfs(void) +{ + int ret; + + ec_legacy_kobj = kobject_create_and_add("EcControl", NULL); + if (!ec_legacy_kobj) + return -ENOMEM; + + ret = sysfs_create_group(ec_legacy_kobj, &ec_attr_group); + if (ret) + kobject_put(ec_legacy_kobj); + + return ret; +} + +void ec_release_legacy_sysfs(void) +{ + sysfs_remove_group(ec_legacy_kobj, &ec_attr_group); + kobject_put(ec_legacy_kobj); +} + +module_init(ec_create_legacy_sysfs); +module_exit(ec_release_legacy_sysfs); + +MODULE_DESCRIPTION("Acer A50x EC legacy sysfs module"); +MODULE_AUTHOR("Dmitry Osipenko "); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/a500/ec/ec.h b/drivers/staging/a500/ec/ec.h new file mode 100644 index 000000000000..ee7617dc6a89 --- /dev/null +++ b/drivers/staging/a500/ec/ec.h @@ -0,0 +1,32 @@ +/* + * MFD driver for Acer A50x embedded controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __MFD_A500_EC_H +#define __MFD_A500_EC_H + +struct ec_reg_data { + u8 addr; + u16 timeout; +}; + +#define EC_REG_DATA(_name, _addr, _timeout) \ +static struct ec_reg_data ec_##_name##_ = { \ + .addr = _addr, \ + .timeout = _timeout, \ +}; \ +static struct ec_reg_data *_name = &ec_##_name##_; + +extern int ec_read_word_data_locked(struct ec_reg_data *reg_data); +extern int ec_read_word_data(struct ec_reg_data *reg_data); +extern int ec_write_word_data_locked(struct ec_reg_data *reg_data, u16 value); +extern int ec_write_word_data(struct ec_reg_data *reg_data, u16 value); +extern void ec_lock(void); +extern void ec_unlock(void); + +#endif /* __MFD_A500_EC_H */