Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 1 | /* |
Kuppuswamy Sathyanarayanan | 05454c2 | 2013-10-17 15:35:27 -0700 | [diff] [blame] | 2 | * intel-mid.c: Intel MID platform setup code |
Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 3 | * |
Kuppuswamy Sathyanarayanan | 05454c2 | 2013-10-17 15:35:27 -0700 | [diff] [blame] | 4 | * (C) Copyright 2008, 2012 Intel Corporation |
Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 5 | * Author: Jacob Pan (jacob.jun.pan@intel.com) |
Kuppuswamy Sathyanarayanan | 05454c2 | 2013-10-17 15:35:27 -0700 | [diff] [blame] | 6 | * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> |
Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 7 | * |
| 8 | * This program is free software; you can redistribute it and/or |
| 9 | * modify it under the terms of the GNU General Public License |
| 10 | * as published by the Free Software Foundation; version 2 |
| 11 | * of the License. |
| 12 | */ |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 13 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 14 | #define pr_fmt(fmt) "intel_mid: " fmt |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 15 | |
Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 16 | #include <linux/init.h> |
Jacob Pan | 16ab539 | 2010-02-12 03:08:30 -0800 | [diff] [blame] | 17 | #include <linux/kernel.h> |
Feng Tang | efe3ed9 | 2011-08-26 11:25:14 +0100 | [diff] [blame] | 18 | #include <linux/interrupt.h> |
| 19 | #include <linux/scatterlist.h> |
Jacob Pan | 16ab539 | 2010-02-12 03:08:30 -0800 | [diff] [blame] | 20 | #include <linux/sfi.h> |
| 21 | #include <linux/irq.h> |
Feng Tang | cf08945 | 2010-02-12 03:37:38 -0800 | [diff] [blame] | 22 | #include <linux/module.h> |
Alan Cox | 42c2544 | 2011-09-07 16:06:51 +0300 | [diff] [blame] | 23 | #include <linux/notifier.h> |
Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 24 | |
| 25 | #include <asm/setup.h> |
Jacob Pan | 16ab539 | 2010-02-12 03:08:30 -0800 | [diff] [blame] | 26 | #include <asm/mpspec_def.h> |
| 27 | #include <asm/hw_irq.h> |
| 28 | #include <asm/apic.h> |
| 29 | #include <asm/io_apic.h> |
Kuppuswamy Sathyanarayanan | 05454c2 | 2013-10-17 15:35:27 -0700 | [diff] [blame] | 30 | #include <asm/intel-mid.h> |
| 31 | #include <asm/intel_mid_vrtc.h> |
Jacob Pan | 5b78b67 | 2010-02-12 02:29:11 -0800 | [diff] [blame] | 32 | #include <asm/io.h> |
| 33 | #include <asm/i8259.h> |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 34 | #include <asm/intel_scu_ipc.h> |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 35 | #include <asm/apb_timer.h> |
Alek Du | cfb505a | 2010-11-10 16:50:08 +0000 | [diff] [blame] | 36 | #include <asm/reboot.h> |
Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 37 | |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 38 | /* |
| 39 | * the clockevent devices on Moorestown/Medfield can be APBT or LAPIC clock, |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 40 | * cmdline option x86_intel_mid_timer can be used to override the configuration |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 41 | * to prefer one or the other. |
| 42 | * at runtime, there are basically three timer configurations: |
| 43 | * 1. per cpu apbt clock only |
| 44 | * 2. per cpu always-on lapic clocks only, this is Penwell/Medfield only |
| 45 | * 3. per cpu lapic clock (C3STOP) and one apbt clock, with broadcast. |
| 46 | * |
| 47 | * by default (without cmdline option), platform code first detects cpu type |
| 48 | * to see if we are on lincroft or penwell, then set up both lapic or apbt |
| 49 | * clocks accordingly. |
| 50 | * i.e. by default, medfield uses configuration #2, moorestown uses #1. |
| 51 | * config #3 is supported but not recommended on medfield. |
| 52 | * |
| 53 | * rating and feature summary: |
| 54 | * lapic (with C3STOP) --------- 100 |
| 55 | * apbt (always-on) ------------ 110 |
| 56 | * lapic (always-on,ARAT) ------ 150 |
| 57 | */ |
| 58 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 59 | enum intel_mid_timer_options intel_mid_timer_options; |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 60 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 61 | enum intel_mid_cpu_type __intel_mid_cpu_chip; |
| 62 | EXPORT_SYMBOL_GPL(__intel_mid_cpu_chip); |
Jacob Pan | a0c173b | 2010-05-19 12:01:24 -0700 | [diff] [blame] | 63 | |
Kuppuswamy Sathyanarayanan | 49c72a0 | 2013-10-17 15:35:32 -0700 | [diff] [blame] | 64 | static void __init ipc_device_handler(struct sfi_device_table_entry *pentry, |
| 65 | struct devs_id *dev); |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 66 | static void intel_mid_power_off(void) |
Jacob Pan | 48bc556 | 2011-11-16 16:07:22 +0000 | [diff] [blame] | 67 | { |
Jacob Pan | 48bc556 | 2011-11-16 16:07:22 +0000 | [diff] [blame] | 68 | } |
| 69 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 70 | static void intel_mid_reboot(void) |
Jacob Pan | 48bc556 | 2011-11-16 16:07:22 +0000 | [diff] [blame] | 71 | { |
Alan Cox | 1a8359e | 2012-01-26 17:33:30 +0000 | [diff] [blame] | 72 | intel_scu_ipc_simple_command(IPCMSG_COLD_BOOT, 0); |
Jacob Pan | 48bc556 | 2011-11-16 16:07:22 +0000 | [diff] [blame] | 73 | } |
| 74 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 75 | static unsigned long __init intel_mid_calibrate_tsc(void) |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 76 | { |
Alan Cox | 1a8359e | 2012-01-26 17:33:30 +0000 | [diff] [blame] | 77 | unsigned long fast_calibrate; |
| 78 | u32 lo, hi, ratio, fsb; |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 79 | |
Alan Cox | 1a8359e | 2012-01-26 17:33:30 +0000 | [diff] [blame] | 80 | rdmsr(MSR_IA32_PERF_STATUS, lo, hi); |
| 81 | pr_debug("IA32 perf status is 0x%x, 0x%0x\n", lo, hi); |
| 82 | ratio = (hi >> 8) & 0x1f; |
| 83 | pr_debug("ratio is %d\n", ratio); |
| 84 | if (!ratio) { |
| 85 | pr_err("read a zero ratio, should be incorrect!\n"); |
| 86 | pr_err("force tsc ratio to 16 ...\n"); |
| 87 | ratio = 16; |
Dirk Brandewie | 0a91532 | 2011-11-10 13:42:53 +0000 | [diff] [blame] | 88 | } |
Alan Cox | 1a8359e | 2012-01-26 17:33:30 +0000 | [diff] [blame] | 89 | rdmsr(MSR_FSB_FREQ, lo, hi); |
| 90 | if ((lo & 0x7) == 0x7) |
| 91 | fsb = PENWELL_FSB_FREQ_83SKU; |
| 92 | else |
| 93 | fsb = PENWELL_FSB_FREQ_100SKU; |
| 94 | fast_calibrate = ratio * fsb; |
| 95 | pr_debug("read penwell tsc %lu khz\n", fast_calibrate); |
| 96 | lapic_timer_frequency = fsb * 1000 / HZ; |
| 97 | /* mark tsc clocksource as reliable */ |
| 98 | set_cpu_cap(&boot_cpu_data, X86_FEATURE_TSC_RELIABLE); |
Kuppuswamy Sathyanarayanan | 05454c2 | 2013-10-17 15:35:27 -0700 | [diff] [blame] | 99 | |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 100 | if (fast_calibrate) |
| 101 | return fast_calibrate; |
| 102 | |
| 103 | return 0; |
| 104 | } |
| 105 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 106 | static void __init intel_mid_time_init(void) |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 107 | { |
Jacob Pan | 7f05dec | 2010-11-09 11:28:43 +0000 | [diff] [blame] | 108 | sfi_table_parse(SFI_SIG_MTMR, NULL, NULL, sfi_parse_mtmr); |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 109 | switch (intel_mid_timer_options) { |
| 110 | case INTEL_MID_TIMER_APBT_ONLY: |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 111 | break; |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 112 | case INTEL_MID_TIMER_LAPIC_APBT: |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 113 | x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock; |
| 114 | x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock; |
| 115 | break; |
| 116 | default: |
| 117 | if (!boot_cpu_has(X86_FEATURE_ARAT)) |
| 118 | break; |
| 119 | x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock; |
| 120 | x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock; |
| 121 | return; |
| 122 | } |
| 123 | /* we need at least one APB timer */ |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 124 | pre_init_apic_IRQ0(); |
| 125 | apbt_time_init(); |
| 126 | } |
| 127 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 128 | static void __cpuinit intel_mid_arch_setup(void) |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 129 | { |
Jacob Pan | a0c173b | 2010-05-19 12:01:24 -0700 | [diff] [blame] | 130 | if (boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 0x27) |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 131 | __intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_PENWELL; |
Jacob Pan | a0c173b | 2010-05-19 12:01:24 -0700 | [diff] [blame] | 132 | else { |
Alan Cox | 1a8359e | 2012-01-26 17:33:30 +0000 | [diff] [blame] | 133 | pr_err("Unknown Intel MID CPU (%d:%d), default to Penwell\n", |
Jacob Pan | a0c173b | 2010-05-19 12:01:24 -0700 | [diff] [blame] | 134 | boot_cpu_data.x86, boot_cpu_data.x86_model); |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 135 | __intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_PENWELL; |
Jacob Pan | a0c173b | 2010-05-19 12:01:24 -0700 | [diff] [blame] | 136 | } |
Jacob Pan | a0c173b | 2010-05-19 12:01:24 -0700 | [diff] [blame] | 137 | } |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 138 | |
Feng Tang | 6d2cce6 | 2010-07-05 23:03:19 +0800 | [diff] [blame] | 139 | /* MID systems don't have i8042 controller */ |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 140 | static int intel_mid_i8042_detect(void) |
Feng Tang | 6d2cce6 | 2010-07-05 23:03:19 +0800 | [diff] [blame] | 141 | { |
| 142 | return 0; |
| 143 | } |
| 144 | |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 145 | /* |
Jacob Pan | 064a59b | 2011-11-10 13:43:05 +0000 | [diff] [blame] | 146 | * Moorestown does not have external NMI source nor port 0x61 to report |
| 147 | * NMI status. The possible NMI sources are from pmu as a result of NMI |
| 148 | * watchdog or lock debug. Reading io port 0x61 results in 0xff which |
| 149 | * misled NMI handler. |
| 150 | */ |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 151 | static unsigned char intel_mid_get_nmi_reason(void) |
Jacob Pan | 064a59b | 2011-11-10 13:43:05 +0000 | [diff] [blame] | 152 | { |
| 153 | return 0; |
| 154 | } |
| 155 | |
| 156 | /* |
Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 157 | * Moorestown specific x86_init function overrides and early setup |
| 158 | * calls. |
| 159 | */ |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 160 | void __init x86_intel_mid_early_setup(void) |
Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 161 | { |
| 162 | x86_init.resources.probe_roms = x86_init_noop; |
| 163 | x86_init.resources.reserve_resources = x86_init_noop; |
Jacob Pan | 5b78b67 | 2010-02-12 02:29:11 -0800 | [diff] [blame] | 164 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 165 | x86_init.timers.timer_init = intel_mid_time_init; |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 166 | x86_init.timers.setup_percpu_clockev = x86_init_noop; |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 167 | |
| 168 | x86_init.irqs.pre_vector_init = x86_init_noop; |
| 169 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 170 | x86_init.oem.arch_setup = intel_mid_arch_setup; |
Jacob Pan | a0c173b | 2010-05-19 12:01:24 -0700 | [diff] [blame] | 171 | |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 172 | x86_cpuinit.setup_percpu_clockev = apbt_setup_secondary_clock; |
Jacob Pan | 3746c6b | 2010-02-12 05:01:12 -0800 | [diff] [blame] | 173 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 174 | x86_platform.calibrate_tsc = intel_mid_calibrate_tsc; |
| 175 | x86_platform.i8042_detect = intel_mid_i8042_detect; |
| 176 | x86_init.timers.wallclock_init = intel_mid_rtc_init; |
| 177 | x86_platform.get_nmi_reason = intel_mid_get_nmi_reason; |
Jacob Pan | 064a59b | 2011-11-10 13:43:05 +0000 | [diff] [blame] | 178 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 179 | x86_init.pci.init = intel_mid_pci_init; |
Jacob Pan | af2730f | 2010-02-12 10:31:47 -0800 | [diff] [blame] | 180 | x86_init.pci.fixup_irqs = x86_init_noop; |
| 181 | |
Jacob Pan | 5b78b67 | 2010-02-12 02:29:11 -0800 | [diff] [blame] | 182 | legacy_pic = &null_legacy_pic; |
Jacob Pan | fea24e2 | 2010-05-14 14:41:20 -0700 | [diff] [blame] | 183 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 184 | pm_power_off = intel_mid_power_off; |
| 185 | machine_ops.emergency_restart = intel_mid_reboot; |
Alek Du | cfb505a | 2010-11-10 16:50:08 +0000 | [diff] [blame] | 186 | |
Jacob Pan | fea24e2 | 2010-05-14 14:41:20 -0700 | [diff] [blame] | 187 | /* Avoid searching for BIOS MP tables */ |
| 188 | x86_init.mpparse.find_smp_config = x86_init_noop; |
| 189 | x86_init.mpparse.get_smp_config = x86_init_uint_noop; |
Jacob Pan | 9d90e49 | 2011-04-08 11:23:00 -0700 | [diff] [blame] | 190 | set_bit(MP_BUS_ISA, mp_bus_not_pci); |
Thomas Gleixner | 3f4110a | 2009-08-29 14:54:20 +0200 | [diff] [blame] | 191 | } |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 192 | |
| 193 | /* |
| 194 | * if user does not want to use per CPU apb timer, just give it a lower rating |
| 195 | * than local apic timer and skip the late per cpu timer init. |
| 196 | */ |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 197 | static inline int __init setup_x86_intel_mid_timer(char *arg) |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 198 | { |
| 199 | if (!arg) |
| 200 | return -EINVAL; |
| 201 | |
| 202 | if (strcmp("apbt_only", arg) == 0) |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 203 | intel_mid_timer_options = INTEL_MID_TIMER_APBT_ONLY; |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 204 | else if (strcmp("lapic_and_apbt", arg) == 0) |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 205 | intel_mid_timer_options = INTEL_MID_TIMER_LAPIC_APBT; |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 206 | else { |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 207 | pr_warn("X86 INTEL_MID timer option %s not recognised" |
| 208 | " use x86_intel_mid_timer=apbt_only or lapic_and_apbt\n", |
Jacob Pan | a875c01 | 2010-05-19 12:01:25 -0700 | [diff] [blame] | 209 | arg); |
| 210 | return -EINVAL; |
| 211 | } |
| 212 | return 0; |
| 213 | } |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 214 | __setup("x86_intel_mid_timer=", setup_x86_intel_mid_timer); |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 215 | |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 216 | /* the offset for the mapping of global gpio pin to irq */ |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 217 | #define INTEL_MID_IRQ_OFFSET 0x100 |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 218 | |
| 219 | static void __init *pmic_gpio_platform_data(void *info) |
| 220 | { |
| 221 | static struct intel_pmic_gpio_platform_data pmic_gpio_pdata; |
| 222 | int gpio_base = get_gpio_by_name("pmic_gpio_base"); |
| 223 | |
| 224 | if (gpio_base == -1) |
| 225 | gpio_base = 64; |
| 226 | pmic_gpio_pdata.gpio_base = gpio_base; |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 227 | pmic_gpio_pdata.irq_base = gpio_base + INTEL_MID_IRQ_OFFSET; |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 228 | pmic_gpio_pdata.gpiointr = 0xffffeff8; |
| 229 | |
| 230 | return &pmic_gpio_pdata; |
| 231 | } |
| 232 | |
| 233 | static void __init *max3111_platform_data(void *info) |
| 234 | { |
| 235 | struct spi_board_info *spi_info = info; |
| 236 | int intr = get_gpio_by_name("max3111_int"); |
| 237 | |
Feng Tang | efe3ed9 | 2011-08-26 11:25:14 +0100 | [diff] [blame] | 238 | spi_info->mode = SPI_MODE_0; |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 239 | if (intr == -1) |
| 240 | return NULL; |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 241 | spi_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 242 | return NULL; |
| 243 | } |
| 244 | |
| 245 | /* we have multiple max7315 on the board ... */ |
| 246 | #define MAX7315_NUM 2 |
| 247 | static void __init *max7315_platform_data(void *info) |
| 248 | { |
| 249 | static struct pca953x_platform_data max7315_pdata[MAX7315_NUM]; |
| 250 | static int nr; |
| 251 | struct pca953x_platform_data *max7315 = &max7315_pdata[nr]; |
| 252 | struct i2c_board_info *i2c_info = info; |
| 253 | int gpio_base, intr; |
| 254 | char base_pin_name[SFI_NAME_LEN + 1]; |
| 255 | char intr_pin_name[SFI_NAME_LEN + 1]; |
| 256 | |
| 257 | if (nr == MAX7315_NUM) { |
| 258 | pr_err("too many max7315s, we only support %d\n", |
| 259 | MAX7315_NUM); |
| 260 | return NULL; |
| 261 | } |
| 262 | /* we have several max7315 on the board, we only need load several |
| 263 | * instances of the same pca953x driver to cover them |
| 264 | */ |
| 265 | strcpy(i2c_info->type, "max7315"); |
| 266 | if (nr++) { |
| 267 | sprintf(base_pin_name, "max7315_%d_base", nr); |
| 268 | sprintf(intr_pin_name, "max7315_%d_int", nr); |
| 269 | } else { |
| 270 | strcpy(base_pin_name, "max7315_base"); |
| 271 | strcpy(intr_pin_name, "max7315_int"); |
| 272 | } |
| 273 | |
| 274 | gpio_base = get_gpio_by_name(base_pin_name); |
| 275 | intr = get_gpio_by_name(intr_pin_name); |
| 276 | |
| 277 | if (gpio_base == -1) |
| 278 | return NULL; |
| 279 | max7315->gpio_base = gpio_base; |
| 280 | if (intr != -1) { |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 281 | i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| 282 | max7315->irq_base = gpio_base + INTEL_MID_IRQ_OFFSET; |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 283 | } else { |
| 284 | i2c_info->irq = -1; |
| 285 | max7315->irq_base = -1; |
| 286 | } |
| 287 | return max7315; |
| 288 | } |
| 289 | |
Jekyll Lai | 28744b3 | 2011-11-16 18:01:20 +0000 | [diff] [blame] | 290 | static void *tca6416_platform_data(void *info) |
| 291 | { |
| 292 | static struct pca953x_platform_data tca6416; |
| 293 | struct i2c_board_info *i2c_info = info; |
| 294 | int gpio_base, intr; |
| 295 | char base_pin_name[SFI_NAME_LEN + 1]; |
| 296 | char intr_pin_name[SFI_NAME_LEN + 1]; |
| 297 | |
| 298 | strcpy(i2c_info->type, "tca6416"); |
| 299 | strcpy(base_pin_name, "tca6416_base"); |
| 300 | strcpy(intr_pin_name, "tca6416_int"); |
| 301 | |
| 302 | gpio_base = get_gpio_by_name(base_pin_name); |
| 303 | intr = get_gpio_by_name(intr_pin_name); |
| 304 | |
| 305 | if (gpio_base == -1) |
| 306 | return NULL; |
| 307 | tca6416.gpio_base = gpio_base; |
| 308 | if (intr != -1) { |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 309 | i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| 310 | tca6416.irq_base = gpio_base + INTEL_MID_IRQ_OFFSET; |
Jekyll Lai | 28744b3 | 2011-11-16 18:01:20 +0000 | [diff] [blame] | 311 | } else { |
| 312 | i2c_info->irq = -1; |
| 313 | tca6416.irq_base = -1; |
| 314 | } |
| 315 | return &tca6416; |
| 316 | } |
| 317 | |
| 318 | static void *mpu3050_platform_data(void *info) |
| 319 | { |
| 320 | struct i2c_board_info *i2c_info = info; |
| 321 | int intr = get_gpio_by_name("mpu3050_int"); |
| 322 | |
| 323 | if (intr == -1) |
| 324 | return NULL; |
| 325 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 326 | i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
Jekyll Lai | 28744b3 | 2011-11-16 18:01:20 +0000 | [diff] [blame] | 327 | return NULL; |
| 328 | } |
| 329 | |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 330 | static void __init *emc1403_platform_data(void *info) |
| 331 | { |
| 332 | static short intr2nd_pdata; |
| 333 | struct i2c_board_info *i2c_info = info; |
| 334 | int intr = get_gpio_by_name("thermal_int"); |
| 335 | int intr2nd = get_gpio_by_name("thermal_alert"); |
| 336 | |
| 337 | if (intr == -1 || intr2nd == -1) |
| 338 | return NULL; |
| 339 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 340 | i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| 341 | intr2nd_pdata = intr2nd + INTEL_MID_IRQ_OFFSET; |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 342 | |
| 343 | return &intr2nd_pdata; |
| 344 | } |
| 345 | |
| 346 | static void __init *lis331dl_platform_data(void *info) |
| 347 | { |
| 348 | static short intr2nd_pdata; |
| 349 | struct i2c_board_info *i2c_info = info; |
| 350 | int intr = get_gpio_by_name("accel_int"); |
| 351 | int intr2nd = get_gpio_by_name("accel_2"); |
| 352 | |
| 353 | if (intr == -1 || intr2nd == -1) |
| 354 | return NULL; |
| 355 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 356 | i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; |
| 357 | intr2nd_pdata = intr2nd + INTEL_MID_IRQ_OFFSET; |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 358 | |
| 359 | return &intr2nd_pdata; |
| 360 | } |
| 361 | |
Vinod Koul | 8607153 | 2010-11-10 17:40:48 +0000 | [diff] [blame] | 362 | static void __init *no_platform_data(void *info) |
| 363 | { |
| 364 | return NULL; |
| 365 | } |
| 366 | |
Mika Westerberg | 360545c | 2011-10-18 12:41:22 +0300 | [diff] [blame] | 367 | static struct resource msic_resources[] = { |
| 368 | { |
| 369 | .start = INTEL_MSIC_IRQ_PHYS_BASE, |
| 370 | .end = INTEL_MSIC_IRQ_PHYS_BASE + 64 - 1, |
| 371 | .flags = IORESOURCE_MEM, |
| 372 | }, |
| 373 | }; |
| 374 | |
| 375 | static struct intel_msic_platform_data msic_pdata; |
| 376 | |
| 377 | static struct platform_device msic_device = { |
| 378 | .name = "intel_msic", |
| 379 | .id = -1, |
| 380 | .dev = { |
| 381 | .platform_data = &msic_pdata, |
| 382 | }, |
| 383 | .num_resources = ARRAY_SIZE(msic_resources), |
| 384 | .resource = msic_resources, |
| 385 | }; |
| 386 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 387 | static inline bool intel_mid_has_msic(void) |
Mika Westerberg | 360545c | 2011-10-18 12:41:22 +0300 | [diff] [blame] | 388 | { |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 389 | return intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_PENWELL; |
Mika Westerberg | 360545c | 2011-10-18 12:41:22 +0300 | [diff] [blame] | 390 | } |
| 391 | |
| 392 | static int msic_scu_status_change(struct notifier_block *nb, |
| 393 | unsigned long code, void *data) |
| 394 | { |
| 395 | if (code == SCU_DOWN) { |
| 396 | platform_device_unregister(&msic_device); |
| 397 | return 0; |
| 398 | } |
| 399 | |
| 400 | return platform_device_register(&msic_device); |
| 401 | } |
| 402 | |
| 403 | static int __init msic_init(void) |
| 404 | { |
| 405 | static struct notifier_block msic_scu_notifier = { |
| 406 | .notifier_call = msic_scu_status_change, |
| 407 | }; |
| 408 | |
| 409 | /* |
| 410 | * We need to be sure that the SCU IPC is ready before MSIC device |
| 411 | * can be registered. |
| 412 | */ |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 413 | if (intel_mid_has_msic()) |
Mika Westerberg | 360545c | 2011-10-18 12:41:22 +0300 | [diff] [blame] | 414 | intel_scu_notifier_add(&msic_scu_notifier); |
| 415 | |
| 416 | return 0; |
| 417 | } |
| 418 | arch_initcall(msic_init); |
| 419 | |
| 420 | /* |
| 421 | * msic_generic_platform_data - sets generic platform data for the block |
| 422 | * @info: pointer to the SFI device table entry for this block |
| 423 | * @block: MSIC block |
| 424 | * |
| 425 | * Function sets IRQ number from the SFI table entry for given device to |
| 426 | * the MSIC platform data. |
| 427 | */ |
| 428 | static void *msic_generic_platform_data(void *info, enum intel_msic_block block) |
| 429 | { |
| 430 | struct sfi_device_table_entry *entry = info; |
| 431 | |
| 432 | BUG_ON(block < 0 || block >= INTEL_MSIC_BLOCK_LAST); |
| 433 | msic_pdata.irq[block] = entry->irq; |
| 434 | |
| 435 | return no_platform_data(info); |
| 436 | } |
| 437 | |
| 438 | static void *msic_battery_platform_data(void *info) |
| 439 | { |
| 440 | return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_BATTERY); |
| 441 | } |
| 442 | |
| 443 | static void *msic_gpio_platform_data(void *info) |
| 444 | { |
| 445 | static struct intel_msic_gpio_pdata pdata; |
| 446 | int gpio = get_gpio_by_name("msic_gpio_base"); |
| 447 | |
| 448 | if (gpio < 0) |
| 449 | return NULL; |
| 450 | |
| 451 | pdata.gpio_base = gpio; |
| 452 | msic_pdata.gpio = &pdata; |
| 453 | |
| 454 | return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_GPIO); |
| 455 | } |
| 456 | |
| 457 | static void *msic_audio_platform_data(void *info) |
| 458 | { |
| 459 | struct platform_device *pdev; |
| 460 | |
| 461 | pdev = platform_device_register_simple("sst-platform", -1, NULL, 0); |
| 462 | if (IS_ERR(pdev)) { |
| 463 | pr_err("failed to create audio platform device\n"); |
| 464 | return NULL; |
| 465 | } |
| 466 | |
| 467 | return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_AUDIO); |
| 468 | } |
| 469 | |
| 470 | static void *msic_power_btn_platform_data(void *info) |
| 471 | { |
| 472 | return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_POWER_BTN); |
| 473 | } |
| 474 | |
| 475 | static void *msic_ocd_platform_data(void *info) |
| 476 | { |
| 477 | static struct intel_msic_ocd_pdata pdata; |
| 478 | int gpio = get_gpio_by_name("ocd_gpio"); |
| 479 | |
| 480 | if (gpio < 0) |
| 481 | return NULL; |
| 482 | |
| 483 | pdata.gpio = gpio; |
| 484 | msic_pdata.ocd = &pdata; |
| 485 | |
| 486 | return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_OCD); |
| 487 | } |
| 488 | |
Mika Westerberg | ecfdb0a | 2012-01-26 17:36:08 +0000 | [diff] [blame] | 489 | static void *msic_thermal_platform_data(void *info) |
| 490 | { |
| 491 | return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_THERMAL); |
| 492 | } |
| 493 | |
Kirill A. Shutemov | 026abc3 | 2012-03-08 16:02:20 +0000 | [diff] [blame] | 494 | /* tc35876x DSI-LVDS bridge chip and panel platform data */ |
| 495 | static void *tc35876x_platform_data(void *data) |
| 496 | { |
Kuppuswamy Sathyanarayanan | d805930 | 2013-10-17 15:35:26 -0700 | [diff] [blame] | 497 | static struct tc35876x_platform_data pdata; |
Kirill A. Shutemov | 026abc3 | 2012-03-08 16:02:20 +0000 | [diff] [blame] | 498 | |
Kuppuswamy Sathyanarayanan | d805930 | 2013-10-17 15:35:26 -0700 | [diff] [blame] | 499 | /* gpio pins set to -1 will not be used by the driver */ |
| 500 | pdata.gpio_bridge_reset = get_gpio_by_name("LCMB_RXEN"); |
| 501 | pdata.gpio_panel_bl_en = get_gpio_by_name("6S6P_BL_EN"); |
| 502 | pdata.gpio_panel_vadd = get_gpio_by_name("EN_VREG_LCD_V3P3"); |
Kirill A. Shutemov | 026abc3 | 2012-03-08 16:02:20 +0000 | [diff] [blame] | 503 | |
Kuppuswamy Sathyanarayanan | d805930 | 2013-10-17 15:35:26 -0700 | [diff] [blame] | 504 | return &pdata; |
Kirill A. Shutemov | 026abc3 | 2012-03-08 16:02:20 +0000 | [diff] [blame] | 505 | } |
| 506 | |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 507 | static const struct devs_id __initconst device_ids[] = { |
Kuppuswamy Sathyanarayanan | 3fd79ae | 2013-10-17 15:35:31 -0700 | [diff] [blame] | 508 | {"bma023", SFI_DEV_TYPE_I2C, 1, &no_platform_data, NULL}, |
| 509 | {"pmic_gpio", SFI_DEV_TYPE_SPI, 1, &pmic_gpio_platform_data, NULL}, |
Kuppuswamy Sathyanarayanan | 49c72a0 | 2013-10-17 15:35:32 -0700 | [diff] [blame] | 510 | {"pmic_gpio", SFI_DEV_TYPE_IPC, 1, &pmic_gpio_platform_data, &ipc_device_handler}, |
Kuppuswamy Sathyanarayanan | 3fd79ae | 2013-10-17 15:35:31 -0700 | [diff] [blame] | 511 | {"spi_max3111", SFI_DEV_TYPE_SPI, 0, &max3111_platform_data, NULL}, |
| 512 | {"i2c_max7315", SFI_DEV_TYPE_I2C, 1, &max7315_platform_data, NULL}, |
| 513 | {"i2c_max7315_2", SFI_DEV_TYPE_I2C, 1, &max7315_platform_data, NULL}, |
| 514 | {"tca6416", SFI_DEV_TYPE_I2C, 1, &tca6416_platform_data, NULL}, |
| 515 | {"emc1403", SFI_DEV_TYPE_I2C, 1, &emc1403_platform_data, NULL}, |
| 516 | {"i2c_accel", SFI_DEV_TYPE_I2C, 0, &lis331dl_platform_data, NULL}, |
Kuppuswamy Sathyanarayanan | 49c72a0 | 2013-10-17 15:35:32 -0700 | [diff] [blame] | 517 | {"pmic_audio", SFI_DEV_TYPE_IPC, 1, &no_platform_data, &ipc_device_handler}, |
Kuppuswamy Sathyanarayanan | 3fd79ae | 2013-10-17 15:35:31 -0700 | [diff] [blame] | 518 | {"mpu3050", SFI_DEV_TYPE_I2C, 1, &mpu3050_platform_data, NULL}, |
| 519 | {"i2c_disp_brig", SFI_DEV_TYPE_I2C, 0, &tc35876x_platform_data, NULL}, |
Mika Westerberg | 360545c | 2011-10-18 12:41:22 +0300 | [diff] [blame] | 520 | |
| 521 | /* MSIC subdevices */ |
Kuppuswamy Sathyanarayanan | 49c72a0 | 2013-10-17 15:35:32 -0700 | [diff] [blame] | 522 | {"msic_battery", SFI_DEV_TYPE_IPC, 1, &msic_battery_platform_data, &ipc_device_handler}, |
| 523 | {"msic_gpio", SFI_DEV_TYPE_IPC, 1, &msic_gpio_platform_data, &ipc_device_handler}, |
| 524 | {"msic_audio", SFI_DEV_TYPE_IPC, 1, &msic_audio_platform_data, &ipc_device_handler}, |
| 525 | {"msic_power_btn", SFI_DEV_TYPE_IPC, 1, &msic_power_btn_platform_data, &ipc_device_handler}, |
| 526 | {"msic_ocd", SFI_DEV_TYPE_IPC, 1, &msic_ocd_platform_data, &ipc_device_handler}, |
| 527 | {"msic_thermal", SFI_DEV_TYPE_IPC, 1, &msic_thermal_platform_data, &ipc_device_handler}, |
Kuppuswamy Sathyanarayanan | 3fd79ae | 2013-10-17 15:35:31 -0700 | [diff] [blame] | 528 | { 0 } |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 529 | }; |
| 530 | |
Kuppuswamy Sathyanarayanan | 49c72a0 | 2013-10-17 15:35:32 -0700 | [diff] [blame] | 531 | static void __init ipc_device_handler(struct sfi_device_table_entry *pentry, |
| 532 | struct devs_id *dev) |
| 533 | { |
| 534 | struct platform_device *pdev; |
| 535 | void *pdata = NULL; |
| 536 | static struct resource res __initdata = { |
| 537 | .name = "IRQ", |
| 538 | .flags = IORESOURCE_IRQ, |
| 539 | }; |
| 540 | |
| 541 | pr_debug("IPC bus, name = %16.16s, irq = 0x%2x\n", |
| 542 | pentry->name, pentry->irq); |
| 543 | |
| 544 | /* |
| 545 | * We need to call platform init of IPC devices to fill misc_pdata |
| 546 | * structure. It will be used in msic_init for initialization. |
| 547 | */ |
| 548 | if (dev != NULL) |
| 549 | pdata = dev->get_platform_data(pentry); |
| 550 | |
| 551 | /* |
| 552 | * On Medfield the platform device creation is handled by the MSIC |
| 553 | * MFD driver so we don't need to do it here. |
| 554 | */ |
| 555 | if (intel_mid_has_msic()) |
| 556 | return; |
| 557 | |
| 558 | pdev = platform_device_alloc(pentry->name, 0); |
| 559 | if (pdev == NULL) { |
| 560 | pr_err("out of memory for SFI platform device '%s'.\n", |
| 561 | pentry->name); |
| 562 | return; |
| 563 | } |
| 564 | res.start = pentry->irq; |
| 565 | platform_device_add_resources(pdev, &res, 1); |
| 566 | |
| 567 | pdev->dev.platform_data = pdata; |
| 568 | intel_scu_device_register(pdev); |
| 569 | } |
| 570 | |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 571 | |
| 572 | /* |
| 573 | * we will search these buttons in SFI GPIO table (by name) |
| 574 | * and register them dynamically. Please add all possible |
| 575 | * buttons here, we will shrink them if no GPIO found. |
| 576 | */ |
| 577 | static struct gpio_keys_button gpio_button[] = { |
| 578 | {KEY_POWER, -1, 1, "power_btn", EV_KEY, 0, 3000}, |
| 579 | {KEY_PROG1, -1, 1, "prog_btn1", EV_KEY, 0, 20}, |
| 580 | {KEY_PROG2, -1, 1, "prog_btn2", EV_KEY, 0, 20}, |
| 581 | {SW_LID, -1, 1, "lid_switch", EV_SW, 0, 20}, |
| 582 | {KEY_VOLUMEUP, -1, 1, "vol_up", EV_KEY, 0, 20}, |
| 583 | {KEY_VOLUMEDOWN, -1, 1, "vol_down", EV_KEY, 0, 20}, |
| 584 | {KEY_CAMERA, -1, 1, "camera_full", EV_KEY, 0, 20}, |
| 585 | {KEY_CAMERA_FOCUS, -1, 1, "camera_half", EV_KEY, 0, 20}, |
| 586 | {SW_KEYPAD_SLIDE, -1, 1, "MagSw1", EV_SW, 0, 20}, |
| 587 | {SW_KEYPAD_SLIDE, -1, 1, "MagSw2", EV_SW, 0, 20}, |
| 588 | }; |
| 589 | |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 590 | static struct gpio_keys_platform_data intel_mid_gpio_keys = { |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 591 | .buttons = gpio_button, |
| 592 | .rep = 1, |
| 593 | .nbuttons = -1, /* will fill it after search */ |
| 594 | }; |
| 595 | |
| 596 | static struct platform_device pb_device = { |
| 597 | .name = "gpio-keys", |
| 598 | .id = -1, |
| 599 | .dev = { |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 600 | .platform_data = &intel_mid_gpio_keys, |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 601 | }, |
| 602 | }; |
| 603 | |
| 604 | /* |
| 605 | * Shrink the non-existent buttons, register the gpio button |
| 606 | * device if there is some |
| 607 | */ |
| 608 | static int __init pb_keys_init(void) |
| 609 | { |
| 610 | struct gpio_keys_button *gb = gpio_button; |
| 611 | int i, num, good = 0; |
| 612 | |
| 613 | num = sizeof(gpio_button) / sizeof(struct gpio_keys_button); |
| 614 | for (i = 0; i < num; i++) { |
| 615 | gb[i].gpio = get_gpio_by_name(gb[i].desc); |
Kuppuswamy Sathyanarayanan | d805930 | 2013-10-17 15:35:26 -0700 | [diff] [blame] | 616 | pr_debug("info[%2d]: name = %s, gpio = %d\n", i, gb[i].desc, |
| 617 | gb[i].gpio); |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 618 | if (gb[i].gpio == -1) |
| 619 | continue; |
| 620 | |
| 621 | if (i != good) |
| 622 | gb[good] = gb[i]; |
| 623 | good++; |
| 624 | } |
| 625 | |
| 626 | if (good) { |
Kuppuswamy Sathyanarayanan | 712b6aa | 2013-10-17 15:35:29 -0700 | [diff] [blame] | 627 | intel_mid_gpio_keys.nbuttons = good; |
Feng Tang | 1da4b1c | 2010-11-09 11:22:58 +0000 | [diff] [blame] | 628 | return platform_device_register(&pb_device); |
| 629 | } |
| 630 | return 0; |
| 631 | } |
Kuppuswamy Sathyanarayanan | aeedb37 | 2013-10-17 15:35:33 -0700 | [diff] [blame] | 632 | late_initcall(pb_keys_init); |