diff options
| author | Daniel Drake <dsd@laptop.org> | 2011-06-25 12:34:14 -0400 |
|---|---|---|
| committer | H. Peter Anvin <hpa@linux.intel.com> | 2011-07-06 17:44:38 -0400 |
| commit | 7bc74b3df73776fe06f3df9fafd2d2698e6ca28a (patch) | |
| tree | 166bd6e0b0de947dd278acac03480eb359bff93e | |
| parent | bc4ecd5a5efc2435e6debfb7b279a15ae96697fd (diff) | |
x86, olpc-xo1-sci: Add GPE handler and ebook switch functionality
The EC in the OLPC XO-1 delivers GPE events to provide various
notifications. Add the basic code for GPE/EC event processing and
enable the ebook switch, which can be used as a wakeup source.
Signed-off-by: Daniel Drake <dsd@laptop.org>
Link: http://lkml.kernel.org/r/1309019658-1712-8-git-send-email-dsd@laptop.org
Acked-by: Andres Salomon <dilinger@queued.net>
Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
| -rw-r--r-- | arch/x86/Kconfig | 2 | ||||
| -rw-r--r-- | arch/x86/include/asm/olpc.h | 5 | ||||
| -rw-r--r-- | arch/x86/platform/olpc/olpc-xo1-sci.c | 183 | ||||
| -rw-r--r-- | include/linux/cs5535.h | 22 |
4 files changed, 209 insertions, 3 deletions
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 66b7b9d519d5..88889106ac9b 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig | |||
| @@ -2087,7 +2087,9 @@ config OLPC_XO1_SCI | |||
| 2087 | select MFD_CORE | 2087 | select MFD_CORE |
| 2088 | ---help--- | 2088 | ---help--- |
| 2089 | Add support for SCI-based features of the OLPC XO-1 laptop: | 2089 | Add support for SCI-based features of the OLPC XO-1 laptop: |
| 2090 | - EC-driven system wakeups | ||
| 2090 | - Power button | 2091 | - Power button |
| 2092 | - Ebook switch | ||
| 2091 | 2093 | ||
| 2092 | endif # X86_32 | 2094 | endif # X86_32 |
| 2093 | 2095 | ||
diff --git a/arch/x86/include/asm/olpc.h b/arch/x86/include/asm/olpc.h index 0e56d01907fe..87bdbca72f94 100644 --- a/arch/x86/include/asm/olpc.h +++ b/arch/x86/include/asm/olpc.h | |||
| @@ -111,6 +111,7 @@ extern int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen, | |||
| 111 | #define EC_WRITE_SCI_MASK 0x1b | 111 | #define EC_WRITE_SCI_MASK 0x1b |
| 112 | #define EC_WAKE_UP_WLAN 0x24 | 112 | #define EC_WAKE_UP_WLAN 0x24 |
| 113 | #define EC_WLAN_LEAVE_RESET 0x25 | 113 | #define EC_WLAN_LEAVE_RESET 0x25 |
| 114 | #define EC_READ_EB_MODE 0x2a | ||
| 114 | #define EC_SET_SCI_INHIBIT 0x32 | 115 | #define EC_SET_SCI_INHIBIT 0x32 |
| 115 | #define EC_SET_SCI_INHIBIT_RELEASE 0x34 | 116 | #define EC_SET_SCI_INHIBIT_RELEASE 0x34 |
| 116 | #define EC_WLAN_ENTER_RESET 0x35 | 117 | #define EC_WLAN_ENTER_RESET 0x35 |
| @@ -144,7 +145,7 @@ extern int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen, | |||
| 144 | #define OLPC_GPIO_SMB_CLK 14 | 145 | #define OLPC_GPIO_SMB_CLK 14 |
| 145 | #define OLPC_GPIO_SMB_DATA 15 | 146 | #define OLPC_GPIO_SMB_DATA 15 |
| 146 | #define OLPC_GPIO_WORKAUX geode_gpio(24) | 147 | #define OLPC_GPIO_WORKAUX geode_gpio(24) |
| 147 | #define OLPC_GPIO_LID geode_gpio(26) | 148 | #define OLPC_GPIO_LID 26 |
| 148 | #define OLPC_GPIO_ECSCI geode_gpio(27) | 149 | #define OLPC_GPIO_ECSCI 27 |
| 149 | 150 | ||
| 150 | #endif /* _ASM_X86_OLPC_H */ | 151 | #endif /* _ASM_X86_OLPC_H */ |
diff --git a/arch/x86/platform/olpc/olpc-xo1-sci.c b/arch/x86/platform/olpc/olpc-xo1-sci.c index 8fbf961dae89..63f50506078b 100644 --- a/arch/x86/platform/olpc/olpc-xo1-sci.c +++ b/arch/x86/platform/olpc/olpc-xo1-sci.c | |||
| @@ -12,12 +12,15 @@ | |||
| 12 | */ | 12 | */ |
| 13 | 13 | ||
| 14 | #include <linux/cs5535.h> | 14 | #include <linux/cs5535.h> |
| 15 | #include <linux/device.h> | ||
| 16 | #include <linux/gpio.h> | ||
| 15 | #include <linux/input.h> | 17 | #include <linux/input.h> |
| 16 | #include <linux/interrupt.h> | 18 | #include <linux/interrupt.h> |
| 17 | #include <linux/platform_device.h> | 19 | #include <linux/platform_device.h> |
| 18 | #include <linux/pm.h> | 20 | #include <linux/pm.h> |
| 19 | #include <linux/mfd/core.h> | 21 | #include <linux/mfd/core.h> |
| 20 | #include <linux/suspend.h> | 22 | #include <linux/suspend.h> |
| 23 | #include <linux/workqueue.h> | ||
| 21 | 24 | ||
| 22 | #include <asm/io.h> | 25 | #include <asm/io.h> |
| 23 | #include <asm/msr.h> | 26 | #include <asm/msr.h> |
| @@ -28,8 +31,60 @@ | |||
| 28 | 31 | ||
| 29 | static unsigned long acpi_base; | 32 | static unsigned long acpi_base; |
| 30 | static struct input_dev *power_button_idev; | 33 | static struct input_dev *power_button_idev; |
| 34 | static struct input_dev *ebook_switch_idev; | ||
| 35 | |||
| 31 | static int sci_irq; | 36 | static int sci_irq; |
| 32 | 37 | ||
| 38 | /* Report current ebook switch state through input layer */ | ||
| 39 | static void send_ebook_state(void) | ||
| 40 | { | ||
| 41 | unsigned char state; | ||
| 42 | |||
| 43 | if (olpc_ec_cmd(EC_READ_EB_MODE, NULL, 0, &state, 1)) { | ||
| 44 | pr_err(PFX "failed to get ebook state\n"); | ||
| 45 | return; | ||
| 46 | } | ||
| 47 | |||
| 48 | input_report_switch(ebook_switch_idev, SW_TABLET_MODE, state); | ||
| 49 | input_sync(ebook_switch_idev); | ||
| 50 | } | ||
| 51 | |||
| 52 | /* | ||
| 53 | * Process all items in the EC's SCI queue. | ||
| 54 | * | ||
| 55 | * This is handled in a workqueue because olpc_ec_cmd can be slow (and | ||
| 56 | * can even timeout). | ||
| 57 | * | ||
| 58 | * If propagate_events is false, the queue is drained without events being | ||
| 59 | * generated for the interrupts. | ||
| 60 | */ | ||
| 61 | static void process_sci_queue(bool propagate_events) | ||
| 62 | { | ||
| 63 | int r; | ||
| 64 | u16 data; | ||
| 65 | |||
| 66 | do { | ||
| 67 | r = olpc_ec_sci_query(&data); | ||
| 68 | if (r || !data) | ||
| 69 | break; | ||
| 70 | |||
| 71 | pr_debug(PFX "SCI 0x%x received\n", data); | ||
| 72 | |||
| 73 | if (data == EC_SCI_SRC_EBOOK && propagate_events) | ||
| 74 | send_ebook_state(); | ||
| 75 | } while (data); | ||
| 76 | |||
| 77 | if (r) | ||
| 78 | pr_err(PFX "Failed to clear SCI queue"); | ||
| 79 | } | ||
| 80 | |||
| 81 | static void process_sci_queue_work(struct work_struct *work) | ||
| 82 | { | ||
| 83 | process_sci_queue(true); | ||
| 84 | } | ||
| 85 | |||
| 86 | static DECLARE_WORK(sci_work, process_sci_queue_work); | ||
| 87 | |||
| 33 | static irqreturn_t xo1_sci_intr(int irq, void *dev_id) | 88 | static irqreturn_t xo1_sci_intr(int irq, void *dev_id) |
| 34 | { | 89 | { |
| 35 | struct platform_device *pdev = dev_id; | 90 | struct platform_device *pdev = dev_id; |
| @@ -51,6 +106,11 @@ static irqreturn_t xo1_sci_intr(int irq, void *dev_id) | |||
| 51 | input_sync(power_button_idev); | 106 | input_sync(power_button_idev); |
| 52 | } | 107 | } |
| 53 | 108 | ||
| 109 | if (gpe & CS5536_GPIOM7_PME_FLAG) { /* EC GPIO */ | ||
| 110 | cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS); | ||
| 111 | schedule_work(&sci_work); | ||
| 112 | } | ||
| 113 | |||
| 54 | return IRQ_HANDLED; | 114 | return IRQ_HANDLED; |
| 55 | } | 115 | } |
| 56 | 116 | ||
| @@ -60,6 +120,19 @@ static int xo1_sci_suspend(struct platform_device *pdev, pm_message_t state) | |||
| 60 | olpc_xo1_pm_wakeup_set(CS5536_PM_PWRBTN); | 120 | olpc_xo1_pm_wakeup_set(CS5536_PM_PWRBTN); |
| 61 | else | 121 | else |
| 62 | olpc_xo1_pm_wakeup_clear(CS5536_PM_PWRBTN); | 122 | olpc_xo1_pm_wakeup_clear(CS5536_PM_PWRBTN); |
| 123 | |||
| 124 | if (device_may_wakeup(&ebook_switch_idev->dev)) | ||
| 125 | olpc_ec_wakeup_set(EC_SCI_SRC_EBOOK); | ||
| 126 | else | ||
| 127 | olpc_ec_wakeup_clear(EC_SCI_SRC_EBOOK); | ||
| 128 | |||
| 129 | return 0; | ||
| 130 | } | ||
| 131 | |||
| 132 | static int xo1_sci_resume(struct platform_device *pdev) | ||
| 133 | { | ||
| 134 | /* Enable all EC events */ | ||
| 135 | olpc_ec_mask_write(EC_SCI_SRC_ALL); | ||
| 63 | return 0; | 136 | return 0; |
| 64 | } | 137 | } |
| 65 | 138 | ||
| @@ -104,6 +177,50 @@ static int __devinit setup_sci_interrupt(struct platform_device *pdev) | |||
| 104 | return r; | 177 | return r; |
| 105 | } | 178 | } |
| 106 | 179 | ||
| 180 | static int __devinit setup_ec_sci(void) | ||
| 181 | { | ||
| 182 | int r; | ||
| 183 | |||
| 184 | r = gpio_request(OLPC_GPIO_ECSCI, "OLPC-ECSCI"); | ||
| 185 | if (r) | ||
| 186 | return r; | ||
| 187 | |||
| 188 | gpio_direction_input(OLPC_GPIO_ECSCI); | ||
| 189 | |||
| 190 | /* Clear pending EC SCI events */ | ||
| 191 | cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS); | ||
| 192 | cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_POSITIVE_EDGE_STS); | ||
| 193 | |||
| 194 | /* | ||
| 195 | * Enable EC SCI events, and map them to both a PME and the SCI | ||
| 196 | * interrupt. | ||
| 197 | * | ||
| 198 | * Ordinarily, in addition to functioning as GPIOs, Geode GPIOs can | ||
| 199 | * be mapped to regular interrupts *or* Geode-specific Power | ||
| 200 | * Management Events (PMEs) - events that bring the system out of | ||
| 201 | * suspend. In this case, we want both of those things - the system | ||
| 202 | * wakeup, *and* the ability to get an interrupt when an event occurs. | ||
| 203 | * | ||
| 204 | * To achieve this, we map the GPIO to a PME, and then we use one | ||
| 205 | * of the many generic knobs on the CS5535 PIC to additionally map the | ||
| 206 | * PME to the regular SCI interrupt line. | ||
| 207 | */ | ||
| 208 | cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_EVENTS_ENABLE); | ||
| 209 | |||
| 210 | /* Set the SCI to cause a PME event on group 7 */ | ||
| 211 | cs5535_gpio_setup_event(OLPC_GPIO_ECSCI, 7, 1); | ||
| 212 | |||
| 213 | /* And have group 7 also fire the SCI interrupt */ | ||
| 214 | cs5535_pic_unreqz_select_high(7, sci_irq); | ||
| 215 | |||
| 216 | return 0; | ||
| 217 | } | ||
| 218 | |||
| 219 | static void free_ec_sci(void) | ||
| 220 | { | ||
| 221 | gpio_free(OLPC_GPIO_ECSCI); | ||
| 222 | } | ||
| 223 | |||
| 107 | static int __devinit setup_power_button(struct platform_device *pdev) | 224 | static int __devinit setup_power_button(struct platform_device *pdev) |
| 108 | { | 225 | { |
| 109 | int r; | 226 | int r; |
| @@ -135,6 +252,37 @@ static void free_power_button(void) | |||
| 135 | input_free_device(power_button_idev); | 252 | input_free_device(power_button_idev); |
| 136 | } | 253 | } |
| 137 | 254 | ||
| 255 | static int __devinit setup_ebook_switch(struct platform_device *pdev) | ||
| 256 | { | ||
| 257 | int r; | ||
| 258 | |||
| 259 | ebook_switch_idev = input_allocate_device(); | ||
| 260 | if (!ebook_switch_idev) | ||
| 261 | return -ENOMEM; | ||
| 262 | |||
| 263 | ebook_switch_idev->name = "EBook Switch"; | ||
| 264 | ebook_switch_idev->phys = DRV_NAME "/input1"; | ||
| 265 | set_bit(EV_SW, ebook_switch_idev->evbit); | ||
| 266 | set_bit(SW_TABLET_MODE, ebook_switch_idev->swbit); | ||
| 267 | |||
| 268 | ebook_switch_idev->dev.parent = &pdev->dev; | ||
| 269 | device_set_wakeup_capable(&ebook_switch_idev->dev, true); | ||
| 270 | |||
| 271 | r = input_register_device(ebook_switch_idev); | ||
| 272 | if (r) { | ||
| 273 | dev_err(&pdev->dev, "failed to register ebook switch: %d\n", r); | ||
| 274 | input_free_device(ebook_switch_idev); | ||
| 275 | } | ||
| 276 | |||
| 277 | return r; | ||
| 278 | } | ||
| 279 | |||
| 280 | static void free_ebook_switch(void) | ||
| 281 | { | ||
| 282 | input_unregister_device(ebook_switch_idev); | ||
| 283 | input_free_device(ebook_switch_idev); | ||
| 284 | } | ||
| 285 | |||
| 138 | static int __devinit xo1_sci_probe(struct platform_device *pdev) | 286 | static int __devinit xo1_sci_probe(struct platform_device *pdev) |
| 139 | { | 287 | { |
| 140 | struct resource *res; | 288 | struct resource *res; |
| @@ -159,10 +307,39 @@ static int __devinit xo1_sci_probe(struct platform_device *pdev) | |||
| 159 | if (r) | 307 | if (r) |
| 160 | return r; | 308 | return r; |
| 161 | 309 | ||
| 310 | r = setup_ebook_switch(pdev); | ||
| 311 | if (r) | ||
| 312 | goto err_ebook; | ||
| 313 | |||
| 314 | r = setup_ec_sci(); | ||
| 315 | if (r) | ||
| 316 | goto err_ecsci; | ||
| 317 | |||
| 318 | /* Enable PME generation for EC-generated events */ | ||
| 319 | outl(CS5536_GPIOM7_PME_EN, acpi_base + CS5536_PM_GPE0_EN); | ||
| 320 | |||
| 321 | /* Clear pending events */ | ||
| 322 | outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); | ||
| 323 | process_sci_queue(false); | ||
| 324 | |||
| 325 | /* Initial sync */ | ||
| 326 | send_ebook_state(); | ||
| 327 | |||
| 162 | r = setup_sci_interrupt(pdev); | 328 | r = setup_sci_interrupt(pdev); |
| 163 | if (r) | 329 | if (r) |
| 164 | free_power_button(); | 330 | goto err_sci; |
| 165 | 331 | ||
| 332 | /* Enable all EC events */ | ||
| 333 | olpc_ec_mask_write(EC_SCI_SRC_ALL); | ||
| 334 | |||
| 335 | return r; | ||
| 336 | |||
| 337 | err_sci: | ||
| 338 | free_ec_sci(); | ||
| 339 | err_ecsci: | ||
| 340 | free_ebook_switch(); | ||
| 341 | err_ebook: | ||
| 342 | free_power_button(); | ||
| 166 | return r; | 343 | return r; |
| 167 | } | 344 | } |
| 168 | 345 | ||
| @@ -170,6 +347,9 @@ static int __devexit xo1_sci_remove(struct platform_device *pdev) | |||
| 170 | { | 347 | { |
| 171 | mfd_cell_disable(pdev); | 348 | mfd_cell_disable(pdev); |
| 172 | free_irq(sci_irq, pdev); | 349 | free_irq(sci_irq, pdev); |
| 350 | cancel_work_sync(&sci_work); | ||
| 351 | free_ec_sci(); | ||
| 352 | free_ebook_switch(); | ||
| 173 | free_power_button(); | 353 | free_power_button(); |
| 174 | acpi_base = 0; | 354 | acpi_base = 0; |
| 175 | return 0; | 355 | return 0; |
| @@ -182,6 +362,7 @@ static struct platform_driver xo1_sci_driver = { | |||
| 182 | .probe = xo1_sci_probe, | 362 | .probe = xo1_sci_probe, |
| 183 | .remove = __devexit_p(xo1_sci_remove), | 363 | .remove = __devexit_p(xo1_sci_remove), |
| 184 | .suspend = xo1_sci_suspend, | 364 | .suspend = xo1_sci_suspend, |
| 365 | .resume = xo1_sci_resume, | ||
| 185 | }; | 366 | }; |
| 186 | 367 | ||
| 187 | static int __init xo1_sci_init(void) | 368 | static int __init xo1_sci_init(void) |
diff --git a/include/linux/cs5535.h b/include/linux/cs5535.h index 6f78235cb905..d7e9a7f6ddb0 100644 --- a/include/linux/cs5535.h +++ b/include/linux/cs5535.h | |||
| @@ -11,6 +11,8 @@ | |||
| 11 | #ifndef _CS5535_H | 11 | #ifndef _CS5535_H |
| 12 | #define _CS5535_H | 12 | #define _CS5535_H |
| 13 | 13 | ||
| 14 | #include <asm/msr.h> | ||
| 15 | |||
| 14 | /* MSRs */ | 16 | /* MSRs */ |
| 15 | #define MSR_GLIU_P2D_RO0 0x10000029 | 17 | #define MSR_GLIU_P2D_RO0 0x10000029 |
| 16 | 18 | ||
| @@ -43,6 +45,18 @@ | |||
| 43 | #define MSR_GX_GLD_MSR_CONFIG 0xC0002001 | 45 | #define MSR_GX_GLD_MSR_CONFIG 0xC0002001 |
| 44 | #define MSR_GX_MSR_PADSEL 0xC0002011 | 46 | #define MSR_GX_MSR_PADSEL 0xC0002011 |
| 45 | 47 | ||
| 48 | static inline int cs5535_pic_unreqz_select_high(unsigned int group, | ||
| 49 | unsigned int irq) | ||
| 50 | { | ||
| 51 | uint32_t lo, hi; | ||
| 52 | |||
| 53 | rdmsr(MSR_PIC_ZSEL_HIGH, lo, hi); | ||
| 54 | lo &= ~(0xF << (group * 4)); | ||
| 55 | lo |= (irq & 0xF) << (group * 4); | ||
| 56 | wrmsr(MSR_PIC_ZSEL_HIGH, lo, hi); | ||
| 57 | return 0; | ||
| 58 | } | ||
| 59 | |||
| 46 | /* PIC registers */ | 60 | /* PIC registers */ |
| 47 | #define CS5536_PIC_INT_SEL1 0x4d0 | 61 | #define CS5536_PIC_INT_SEL1 0x4d0 |
| 48 | #define CS5536_PIC_INT_SEL2 0x4d1 | 62 | #define CS5536_PIC_INT_SEL2 0x4d1 |
| @@ -73,6 +87,7 @@ | |||
| 73 | #define CS5536_PM1_EN 0x02 | 87 | #define CS5536_PM1_EN 0x02 |
| 74 | #define CS5536_PM1_CNT 0x08 | 88 | #define CS5536_PM1_CNT 0x08 |
| 75 | #define CS5536_PM_GPE0_STS 0x18 | 89 | #define CS5536_PM_GPE0_STS 0x18 |
| 90 | #define CS5536_PM_GPE0_EN 0x1c | ||
| 76 | 91 | ||
| 77 | /* CS5536_PM1_STS bits */ | 92 | /* CS5536_PM1_STS bits */ |
| 78 | #define CS5536_WAK_FLAG (1 << 15) | 93 | #define CS5536_WAK_FLAG (1 << 15) |
| @@ -81,6 +96,13 @@ | |||
| 81 | /* CS5536_PM1_EN bits */ | 96 | /* CS5536_PM1_EN bits */ |
| 82 | #define CS5536_PM_PWRBTN (1 << 8) | 97 | #define CS5536_PM_PWRBTN (1 << 8) |
| 83 | 98 | ||
| 99 | /* CS5536_PM_GPE0_STS bits */ | ||
| 100 | #define CS5536_GPIOM7_PME_FLAG (1 << 31) | ||
| 101 | #define CS5536_GPIOM6_PME_FLAG (1 << 30) | ||
| 102 | |||
| 103 | /* CS5536_PM_GPE0_EN bits */ | ||
| 104 | #define CS5536_GPIOM7_PME_EN (1 << 31) | ||
| 105 | |||
| 84 | /* VSA2 magic values */ | 106 | /* VSA2 magic values */ |
| 85 | #define VSA_VRC_INDEX 0xAC1C | 107 | #define VSA_VRC_INDEX 0xAC1C |
| 86 | #define VSA_VRC_DATA 0xAC1E | 108 | #define VSA_VRC_DATA 0xAC1E |
