Path: blob/master/arch/x86/platform/olpc/olpc-xo1-sci.c
26489 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* Support for OLPC XO-1 System Control Interrupts (SCI)3*4* Copyright (C) 2010 One Laptop per Child5* Copyright (C) 2006 Red Hat, Inc.6* Copyright (C) 2006 Advanced Micro Devices, Inc.7*/89#include <linux/cs5535.h>10#include <linux/device.h>11#include <linux/gpio.h>12#include <linux/input.h>13#include <linux/interrupt.h>14#include <linux/platform_device.h>15#include <linux/pm.h>16#include <linux/power_supply.h>17#include <linux/suspend.h>18#include <linux/workqueue.h>19#include <linux/olpc-ec.h>2021#include <asm/io.h>22#include <asm/msr.h>23#include <asm/olpc.h>2425#define DRV_NAME "olpc-xo1-sci"26#define PFX DRV_NAME ": "2728static unsigned long acpi_base;29static struct input_dev *power_button_idev;30static struct input_dev *ebook_switch_idev;31static struct input_dev *lid_switch_idev;3233static int sci_irq;3435static bool lid_open;36static bool lid_inverted;37static int lid_wake_mode;3839enum lid_wake_modes {40LID_WAKE_ALWAYS,41LID_WAKE_OPEN,42LID_WAKE_CLOSE,43};4445static const char * const lid_wake_mode_names[] = {46[LID_WAKE_ALWAYS] = "always",47[LID_WAKE_OPEN] = "open",48[LID_WAKE_CLOSE] = "close",49};5051static void battery_status_changed(void)52{53struct power_supply *psy = power_supply_get_by_name("olpc_battery");5455if (psy) {56power_supply_changed(psy);57power_supply_put(psy);58}59}6061static void ac_status_changed(void)62{63struct power_supply *psy = power_supply_get_by_name("olpc_ac");6465if (psy) {66power_supply_changed(psy);67power_supply_put(psy);68}69}7071/* Report current ebook switch state through input layer */72static void send_ebook_state(void)73{74unsigned char state;7576if (olpc_ec_cmd(EC_READ_EB_MODE, NULL, 0, &state, 1)) {77pr_err(PFX "failed to get ebook state\n");78return;79}8081if (test_bit(SW_TABLET_MODE, ebook_switch_idev->sw) == !!state)82return; /* Nothing new to report. */8384input_report_switch(ebook_switch_idev, SW_TABLET_MODE, state);85input_sync(ebook_switch_idev);86pm_wakeup_event(&ebook_switch_idev->dev, 0);87}8889static void flip_lid_inverter(void)90{91/* gpio is high; invert so we'll get l->h event interrupt */92if (lid_inverted)93cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT);94else95cs5535_gpio_set(OLPC_GPIO_LID, GPIO_INPUT_INVERT);96lid_inverted = !lid_inverted;97}9899static void detect_lid_state(void)100{101/*102* the edge detector hookup on the gpio inputs on the geode is103* odd, to say the least. See http://dev.laptop.org/ticket/5703104* for details, but in a nutshell: we don't use the edge105* detectors. instead, we make use of an anomaly: with the both106* edge detectors turned off, we still get an edge event on a107* positive edge transition. to take advantage of this, we use the108* front-end inverter to ensure that that's the edge we're always109* going to see next.110*/111112int state;113114state = cs5535_gpio_isset(OLPC_GPIO_LID, GPIO_READ_BACK);115lid_open = !state ^ !lid_inverted; /* x ^^ y */116if (!state)117return;118119flip_lid_inverter();120}121122/* Report current lid switch state through input layer */123static void send_lid_state(void)124{125if (!!test_bit(SW_LID, lid_switch_idev->sw) == !lid_open)126return; /* Nothing new to report. */127128input_report_switch(lid_switch_idev, SW_LID, !lid_open);129input_sync(lid_switch_idev);130pm_wakeup_event(&lid_switch_idev->dev, 0);131}132133static ssize_t lid_wake_mode_show(struct device *dev,134struct device_attribute *attr, char *buf)135{136const char *mode = lid_wake_mode_names[lid_wake_mode];137return sprintf(buf, "%s\n", mode);138}139static ssize_t lid_wake_mode_set(struct device *dev,140struct device_attribute *attr,141const char *buf, size_t count)142{143int i;144for (i = 0; i < ARRAY_SIZE(lid_wake_mode_names); i++) {145const char *mode = lid_wake_mode_names[i];146if (strlen(mode) != count || strncasecmp(mode, buf, count))147continue;148149lid_wake_mode = i;150return count;151}152return -EINVAL;153}154static DEVICE_ATTR(lid_wake_mode, S_IWUSR | S_IRUGO, lid_wake_mode_show,155lid_wake_mode_set);156157static struct attribute *lid_attrs[] = {158&dev_attr_lid_wake_mode.attr,159NULL,160};161ATTRIBUTE_GROUPS(lid);162163/*164* Process all items in the EC's SCI queue.165*166* This is handled in a workqueue because olpc_ec_cmd can be slow (and167* can even timeout).168*169* If propagate_events is false, the queue is drained without events being170* generated for the interrupts.171*/172static void process_sci_queue(bool propagate_events)173{174int r;175u16 data;176177do {178r = olpc_ec_sci_query(&data);179if (r || !data)180break;181182pr_debug(PFX "SCI 0x%x received\n", data);183184switch (data) {185case EC_SCI_SRC_BATERR:186case EC_SCI_SRC_BATSOC:187case EC_SCI_SRC_BATTERY:188case EC_SCI_SRC_BATCRIT:189battery_status_changed();190break;191case EC_SCI_SRC_ACPWR:192ac_status_changed();193break;194}195196if (data == EC_SCI_SRC_EBOOK && propagate_events)197send_ebook_state();198} while (data);199200if (r)201pr_err(PFX "Failed to clear SCI queue");202}203204static void process_sci_queue_work(struct work_struct *work)205{206process_sci_queue(true);207}208209static DECLARE_WORK(sci_work, process_sci_queue_work);210211static irqreturn_t xo1_sci_intr(int irq, void *dev_id)212{213struct platform_device *pdev = dev_id;214u32 sts;215u32 gpe;216217sts = inl(acpi_base + CS5536_PM1_STS);218outl(sts | 0xffff, acpi_base + CS5536_PM1_STS);219220gpe = inl(acpi_base + CS5536_PM_GPE0_STS);221outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS);222223dev_dbg(&pdev->dev, "sts %x gpe %x\n", sts, gpe);224225if (sts & CS5536_PWRBTN_FLAG) {226if (!(sts & CS5536_WAK_FLAG)) {227/* Only report power button input when it was pressed228* during regular operation (as opposed to when it229* was used to wake the system). */230input_report_key(power_button_idev, KEY_POWER, 1);231input_sync(power_button_idev);232input_report_key(power_button_idev, KEY_POWER, 0);233input_sync(power_button_idev);234}235/* Report the wakeup event in all cases. */236pm_wakeup_event(&power_button_idev->dev, 0);237}238239if ((sts & (CS5536_RTC_FLAG | CS5536_WAK_FLAG)) ==240(CS5536_RTC_FLAG | CS5536_WAK_FLAG)) {241/* When the system is woken by the RTC alarm, report the242* event on the rtc device. */243struct device *rtc = bus_find_device_by_name(244&platform_bus_type, NULL, "rtc_cmos");245if (rtc) {246pm_wakeup_event(rtc, 0);247put_device(rtc);248}249}250251if (gpe & CS5536_GPIOM7_PME_FLAG) { /* EC GPIO */252cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS);253schedule_work(&sci_work);254}255256cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);257cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);258detect_lid_state();259send_lid_state();260261return IRQ_HANDLED;262}263264static int xo1_sci_suspend(struct platform_device *pdev, pm_message_t state)265{266if (device_may_wakeup(&power_button_idev->dev))267olpc_xo1_pm_wakeup_set(CS5536_PM_PWRBTN);268else269olpc_xo1_pm_wakeup_clear(CS5536_PM_PWRBTN);270271if (device_may_wakeup(&ebook_switch_idev->dev))272olpc_ec_wakeup_set(EC_SCI_SRC_EBOOK);273else274olpc_ec_wakeup_clear(EC_SCI_SRC_EBOOK);275276if (!device_may_wakeup(&lid_switch_idev->dev)) {277cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);278} else if ((lid_open && lid_wake_mode == LID_WAKE_OPEN) ||279(!lid_open && lid_wake_mode == LID_WAKE_CLOSE)) {280flip_lid_inverter();281282/* we may have just caused an event */283cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);284cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);285286cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);287}288289return 0;290}291292static int xo1_sci_resume(struct platform_device *pdev)293{294/*295* We don't know what may have happened while we were asleep.296* Reestablish our lid setup so we're sure to catch all transitions.297*/298detect_lid_state();299send_lid_state();300cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);301302/* Enable all EC events */303olpc_ec_mask_write(EC_SCI_SRC_ALL);304305/* Power/battery status might have changed too */306battery_status_changed();307ac_status_changed();308return 0;309}310311static int setup_sci_interrupt(struct platform_device *pdev)312{313u32 lo, hi;314u32 sts;315int r;316317rdmsr(0x51400020, lo, hi);318sci_irq = (lo >> 20) & 15;319320if (sci_irq) {321dev_info(&pdev->dev, "SCI is mapped to IRQ %d\n", sci_irq);322} else {323/* Zero means masked */324dev_info(&pdev->dev, "SCI unmapped. Mapping to IRQ 3\n");325sci_irq = 3;326lo |= 0x00300000;327wrmsrq(0x51400020, lo);328}329330/* Select level triggered in PIC */331if (sci_irq < 8) {332lo = inb(CS5536_PIC_INT_SEL1);333lo |= 1 << sci_irq;334outb(lo, CS5536_PIC_INT_SEL1);335} else {336lo = inb(CS5536_PIC_INT_SEL2);337lo |= 1 << (sci_irq - 8);338outb(lo, CS5536_PIC_INT_SEL2);339}340341/* Enable interesting SCI events, and clear pending interrupts */342sts = inl(acpi_base + CS5536_PM1_STS);343outl(((CS5536_PM_PWRBTN | CS5536_PM_RTC) << 16) | 0xffff,344acpi_base + CS5536_PM1_STS);345346r = request_irq(sci_irq, xo1_sci_intr, 0, DRV_NAME, pdev);347if (r)348dev_err(&pdev->dev, "can't request interrupt\n");349350return r;351}352353static int setup_ec_sci(void)354{355int r;356357r = gpio_request(OLPC_GPIO_ECSCI, "OLPC-ECSCI");358if (r)359return r;360361gpio_direction_input(OLPC_GPIO_ECSCI);362363/* Clear pending EC SCI events */364cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS);365cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_POSITIVE_EDGE_STS);366367/*368* Enable EC SCI events, and map them to both a PME and the SCI369* interrupt.370*371* Ordinarily, in addition to functioning as GPIOs, Geode GPIOs can372* be mapped to regular interrupts *or* Geode-specific Power373* Management Events (PMEs) - events that bring the system out of374* suspend. In this case, we want both of those things - the system375* wakeup, *and* the ability to get an interrupt when an event occurs.376*377* To achieve this, we map the GPIO to a PME, and then we use one378* of the many generic knobs on the CS5535 PIC to additionally map the379* PME to the regular SCI interrupt line.380*/381cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_EVENTS_ENABLE);382383/* Set the SCI to cause a PME event on group 7 */384cs5535_gpio_setup_event(OLPC_GPIO_ECSCI, 7, 1);385386/* And have group 7 also fire the SCI interrupt */387cs5535_pic_unreqz_select_high(7, sci_irq);388389return 0;390}391392static void free_ec_sci(void)393{394gpio_free(OLPC_GPIO_ECSCI);395}396397static int setup_lid_events(void)398{399int r;400401r = gpio_request(OLPC_GPIO_LID, "OLPC-LID");402if (r)403return r;404405gpio_direction_input(OLPC_GPIO_LID);406407cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT);408lid_inverted = 0;409410/* Clear edge detection and event enable for now */411cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);412cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);413cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);414cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);415cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);416417/* Set the LID to cause an PME event on group 6 */418cs5535_gpio_setup_event(OLPC_GPIO_LID, 6, 1);419420/* Set PME group 6 to fire the SCI interrupt */421cs5535_gpio_set_irq(6, sci_irq);422423/* Enable the event */424cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);425426return 0;427}428429static void free_lid_events(void)430{431gpio_free(OLPC_GPIO_LID);432}433434static int setup_power_button(struct platform_device *pdev)435{436int r;437438power_button_idev = input_allocate_device();439if (!power_button_idev)440return -ENOMEM;441442power_button_idev->name = "Power Button";443power_button_idev->phys = DRV_NAME "/input0";444set_bit(EV_KEY, power_button_idev->evbit);445set_bit(KEY_POWER, power_button_idev->keybit);446447power_button_idev->dev.parent = &pdev->dev;448device_init_wakeup(&power_button_idev->dev, 1);449450r = input_register_device(power_button_idev);451if (r) {452dev_err(&pdev->dev, "failed to register power button: %d\n", r);453input_free_device(power_button_idev);454}455456return r;457}458459static void free_power_button(void)460{461input_unregister_device(power_button_idev);462}463464static int setup_ebook_switch(struct platform_device *pdev)465{466int r;467468ebook_switch_idev = input_allocate_device();469if (!ebook_switch_idev)470return -ENOMEM;471472ebook_switch_idev->name = "EBook Switch";473ebook_switch_idev->phys = DRV_NAME "/input1";474set_bit(EV_SW, ebook_switch_idev->evbit);475set_bit(SW_TABLET_MODE, ebook_switch_idev->swbit);476477ebook_switch_idev->dev.parent = &pdev->dev;478device_set_wakeup_capable(&ebook_switch_idev->dev, true);479480r = input_register_device(ebook_switch_idev);481if (r) {482dev_err(&pdev->dev, "failed to register ebook switch: %d\n", r);483input_free_device(ebook_switch_idev);484}485486return r;487}488489static void free_ebook_switch(void)490{491input_unregister_device(ebook_switch_idev);492}493494static int setup_lid_switch(struct platform_device *pdev)495{496int r;497498lid_switch_idev = input_allocate_device();499if (!lid_switch_idev)500return -ENOMEM;501502lid_switch_idev->name = "Lid Switch";503lid_switch_idev->phys = DRV_NAME "/input2";504set_bit(EV_SW, lid_switch_idev->evbit);505set_bit(SW_LID, lid_switch_idev->swbit);506507lid_switch_idev->dev.parent = &pdev->dev;508device_set_wakeup_capable(&lid_switch_idev->dev, true);509510r = input_register_device(lid_switch_idev);511if (r) {512dev_err(&pdev->dev, "failed to register lid switch: %d\n", r);513goto err_register;514}515516return 0;517518err_register:519input_free_device(lid_switch_idev);520return r;521}522523static void free_lid_switch(void)524{525input_unregister_device(lid_switch_idev);526}527528static int xo1_sci_probe(struct platform_device *pdev)529{530struct resource *res;531int r;532533/* don't run on non-XOs */534if (!machine_is_olpc())535return -ENODEV;536537res = platform_get_resource(pdev, IORESOURCE_IO, 0);538if (!res) {539dev_err(&pdev->dev, "can't fetch device resource info\n");540return -EIO;541}542acpi_base = res->start;543544r = setup_power_button(pdev);545if (r)546return r;547548r = setup_ebook_switch(pdev);549if (r)550goto err_ebook;551552r = setup_lid_switch(pdev);553if (r)554goto err_lid;555556r = setup_lid_events();557if (r)558goto err_lidevt;559560r = setup_ec_sci();561if (r)562goto err_ecsci;563564/* Enable PME generation for EC-generated events */565outl(CS5536_GPIOM6_PME_EN | CS5536_GPIOM7_PME_EN,566acpi_base + CS5536_PM_GPE0_EN);567568/* Clear pending events */569outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS);570process_sci_queue(false);571572/* Initial sync */573send_ebook_state();574detect_lid_state();575send_lid_state();576577r = setup_sci_interrupt(pdev);578if (r)579goto err_sci;580581/* Enable all EC events */582olpc_ec_mask_write(EC_SCI_SRC_ALL);583584return r;585586err_sci:587free_ec_sci();588err_ecsci:589free_lid_events();590err_lidevt:591free_lid_switch();592err_lid:593free_ebook_switch();594err_ebook:595free_power_button();596return r;597}598599static void xo1_sci_remove(struct platform_device *pdev)600{601free_irq(sci_irq, pdev);602cancel_work_sync(&sci_work);603free_ec_sci();604free_lid_events();605free_lid_switch();606free_ebook_switch();607free_power_button();608acpi_base = 0;609}610611static struct platform_driver xo1_sci_driver = {612.driver = {613.name = "olpc-xo1-sci-acpi",614.dev_groups = lid_groups,615},616.probe = xo1_sci_probe,617.remove = xo1_sci_remove,618.suspend = xo1_sci_suspend,619.resume = xo1_sci_resume,620};621622static int __init xo1_sci_init(void)623{624return platform_driver_register(&xo1_sci_driver);625}626arch_initcall(xo1_sci_init);627628629