Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/common/Kconfig b/arch/arm/common/Kconfig
index cf82a88..66ed0c3 100644
--- a/arch/arm/common/Kconfig
+++ b/arch/arm/common/Kconfig
@@ -1,5 +1,6 @@
config ARM_GIC
bool
+ select MSM_SHOW_RESUME_IRQ
config ARM_VIC
bool
diff --git a/arch/arm/common/cpaccess.c b/arch/arm/common/cpaccess.c
new file mode 100644
index 0000000..241e339
--- /dev/null
+++ b/arch/arm/common/cpaccess.c
@@ -0,0 +1,253 @@
+/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/sysrq.h>
+#include <linux/time.h>
+#include <linux/proc_fs.h>
+#include <linux/kernel_stat.h>
+#include <linux/uaccess.h>
+#include <linux/sysdev.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/semaphore.h>
+#include <linux/file.h>
+#include <linux/percpu.h>
+#include <linux/string.h>
+#include <linux/smp.h>
+#include <asm/cacheflush.h>
+
+/*
+ * CP parameters
+ */
+struct cp_params {
+ unsigned long cp;
+ unsigned long op1;
+ unsigned long op2;
+ unsigned long crn;
+ unsigned long crm;
+ unsigned long write_value;
+ char rw;
+};
+
+static struct semaphore cp_sem;
+static int cpu;
+
+static DEFINE_PER_CPU(struct cp_params, cp_param)
+ = { 15, 0, 0, 0, 0, 0, 'r' };
+
+static struct sysdev_class cpaccess_sysclass = {
+ .name = "cpaccess",
+};
+
+/*
+ * get_asm_value - Dummy fuction
+ * @write_val: Write value incase of a CP register write operation.
+ *
+ * This function is just a placeholder. The first 2 instructions
+ * will be inserted to perform MRC/MCR instruction and a return.
+ * See do_cpregister_rw function. Value passed to function is
+ * accessed from r0 register.
+ */
+static noinline unsigned long cpaccess_dummy(unsigned long write_val)
+{
+ asm("mrc p15, 0, r0, c0, c0, 0\n\t");
+ asm("bx lr\n\t");
+ return 0xBEEF;
+} __attribute__((aligned(32)))
+
+/*
+ * get_asm_value - Read/Write CP registers
+ * @ret: Pointer to return value in case of CP register
+ * read op.
+ *
+ */
+static void get_asm_value(void *ret)
+{
+ *(unsigned long *)ret =
+ cpaccess_dummy(per_cpu(cp_param.write_value, cpu));
+}
+
+/*
+ * dp_cpregister_rw - Read/Write CP registers
+ * @write: 1 for Write and 0 for Read operation
+ *
+ * Returns value read from CP register
+ */
+static unsigned long do_cpregister_rw(int write)
+{
+ unsigned long opcode, ret, *p_opcode;
+
+ /*
+ * Mask the crn, crm, op1, op2 and cp values so they do not
+ * interfer with other fields of the op code.
+ */
+ per_cpu(cp_param.cp, cpu) &= 0xF;
+ per_cpu(cp_param.crn, cpu) &= 0xF;
+ per_cpu(cp_param.crm, cpu) &= 0xF;
+ per_cpu(cp_param.op1, cpu) &= 0x7;
+ per_cpu(cp_param.op2, cpu) &= 0x7;
+
+ /*
+ * Base MRC opcode for MIDR is EE100010,
+ * MCR is 0xEE000010
+ */
+ opcode = (write == 1 ? 0xEE000010 : 0xEE100010);
+ opcode |= (per_cpu(cp_param.crn, cpu)<<16) |
+ (per_cpu(cp_param.crm, cpu)<<0) |
+ (per_cpu(cp_param.op1, cpu)<<21) |
+ (per_cpu(cp_param.op2, cpu)<<5) |
+ (per_cpu(cp_param.cp, cpu) << 8);
+
+ /*
+ * Grab address of the Dummy function, insert MRC/MCR
+ * instruction and a return instruction ("bx lr"). Do
+ * a D cache clean and I cache invalidate after inserting
+ * new code.
+ */
+ p_opcode = (unsigned long *)&cpaccess_dummy;
+ *p_opcode++ = opcode;
+ *p_opcode-- = 0xE12FFF1E;
+ __cpuc_coherent_kern_range((unsigned long)p_opcode,
+ ((unsigned long)p_opcode + (sizeof(long) * 2)));
+
+#ifdef CONFIG_SMP
+ /*
+ * Use smp_call_function_single to do CPU core specific
+ * get_asm_value function call.
+ */
+ if (smp_call_function_single(cpu, get_asm_value, &ret, 1))
+ printk(KERN_ERR "Error cpaccess smp call single\n");
+#else
+ get_asm_value(&ret);
+#endif
+
+ return ret;
+}
+
+/*
+ * cp_register_write_sysfs - sysfs interface for writing to
+ * CP register
+ * @dev: sys device
+ * @attr: device attribute
+ * @buf: write value
+ * @cnt: not used
+ *
+ */
+static ssize_t cp_register_write_sysfs(struct sys_device *dev,
+ struct sysdev_attribute *attr, const char *buf, size_t cnt)
+{
+ unsigned long op1, op2, crn, crm, cp = 15, write_value, ret;
+ char rw;
+ if (down_timeout(&cp_sem, 6000))
+ return -ERESTARTSYS;
+
+ sscanf(buf, "%lu:%lu:%lu:%lu:%lu:%c:%lx:%d", &cp, &op1, &crn,
+ &crm, &op2, &rw, &write_value, &cpu);
+ per_cpu(cp_param.cp, cpu) = cp;
+ per_cpu(cp_param.op1, cpu) = op1;
+ per_cpu(cp_param.crn, cpu) = crn;
+ per_cpu(cp_param.crm, cpu) = crm;
+ per_cpu(cp_param.op2, cpu) = op2;
+ per_cpu(cp_param.rw, cpu) = rw;
+ per_cpu(cp_param.write_value, cpu) = write_value;
+
+ if (per_cpu(cp_param.rw, cpu) == 'w') {
+ do_cpregister_rw(1);
+ ret = cnt;
+ }
+
+ if ((per_cpu(cp_param.rw, cpu) != 'w') &&
+ (per_cpu(cp_param.rw, cpu) != 'r')) {
+ ret = -1;
+ printk(KERN_INFO "Wrong Entry for 'r' or 'w'. \
+ Use cp:op1:crn:crm:op2:r/w:write_value.\n");
+ }
+
+ return cnt;
+}
+
+/*
+ * cp_register_read_sysfs - sysfs interface for reading CP registers
+ * @dev: sys device
+ * @attr: device attribute
+ * @buf: write value
+ *
+ * Code to read in the CPxx crn, crm, op1, op2 variables, or into
+ * the base MRC opcode, store to executable memory, clean/invalidate
+ * caches and then execute the new instruction and provide the
+ * result to the caller.
+ */
+static ssize_t cp_register_read_sysfs(struct sys_device *dev,
+ struct sysdev_attribute *attr, char *buf)
+{
+ int ret;
+ ret = sprintf(buf, "%lx\n", do_cpregister_rw(0));
+
+ if (cp_sem.count <= 0)
+ up(&cp_sem);
+
+ return ret;
+}
+
+/*
+ * Setup sysfs files
+ */
+SYSDEV_ATTR(cp_rw, 0644, cp_register_read_sysfs,
+ cp_register_write_sysfs);
+
+static struct sys_device device_cpaccess = {
+ .id = 0,
+ .cls = &cpaccess_sysclass,
+};
+
+/*
+ * init_cpaccess_sysfs - initialize sys devices
+ */
+static int __init init_cpaccess_sysfs(void)
+{
+ int error = sysdev_class_register(&cpaccess_sysclass);
+
+ if (!error)
+ error = sysdev_register(&device_cpaccess);
+ else
+ printk(KERN_ERR "Error initializing cpaccess \
+ interface\n");
+
+ if (!error)
+ error = sysdev_create_file(&device_cpaccess,
+ &attr_cp_rw);
+ else {
+ printk(KERN_ERR "Error initializing cpaccess \
+ interface\n");
+ sysdev_unregister(&device_cpaccess);
+ sysdev_class_unregister(&cpaccess_sysclass);
+ }
+
+ sema_init(&cp_sem, 1);
+
+ return error;
+}
+
+static void __exit exit_cpaccess_sysfs(void)
+{
+ sysdev_remove_file(&device_cpaccess, &attr_cp_rw);
+ sysdev_unregister(&device_cpaccess);
+ sysdev_class_unregister(&cpaccess_sysclass);
+}
+
+module_init(init_cpaccess_sysfs);
+module_exit(exit_cpaccess_sysfs);
+MODULE_LICENSE("GPL v2");
diff --git a/arch/arm/common/gic.c b/arch/arm/common/gic.c
index 4ddd0a6..8996f06 100644
--- a/arch/arm/common/gic.c
+++ b/arch/arm/common/gic.c
@@ -28,10 +28,12 @@
#include <linux/smp.h>
#include <linux/cpumask.h>
#include <linux/io.h>
+#include <linux/syscore_ops.h>
#include <asm/irq.h>
#include <asm/mach/irq.h>
#include <asm/hardware/gic.h>
+#include <asm/system.h>
static DEFINE_SPINLOCK(irq_controller_lock);
@@ -42,6 +44,11 @@
unsigned int irq_offset;
void __iomem *dist_base;
void __iomem *cpu_base;
+ unsigned int max_irq;
+#ifdef CONFIG_PM
+ unsigned int wakeup_irqs[32];
+ unsigned int enabled_irqs[32];
+#endif
};
/*
@@ -55,6 +62,7 @@
.irq_retrigger = NULL,
.irq_set_type = NULL,
.irq_set_wake = NULL,
+ .irq_disable = NULL,
};
#ifndef MAX_GIC_NR
@@ -93,6 +101,7 @@
if (gic_arch_extn.irq_mask)
gic_arch_extn.irq_mask(d);
spin_unlock(&irq_controller_lock);
+
}
static void gic_unmask_irq(struct irq_data *d)
@@ -106,6 +115,104 @@
spin_unlock(&irq_controller_lock);
}
+static void gic_disable_irq(struct irq_data *d)
+{
+ if (gic_arch_extn.irq_disable)
+ gic_arch_extn.irq_disable(d);
+}
+
+#ifdef CONFIG_PM
+static int gic_suspend_one(struct gic_chip_data *gic)
+{
+ unsigned int i;
+ void __iomem *base = gic->dist_base;
+
+ for (i = 0; i * 32 < gic->max_irq; i++) {
+ gic->enabled_irqs[i]
+ = readl_relaxed(base + GIC_DIST_ENABLE_SET + i * 4);
+ /* disable all of them */
+ writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4);
+ /* enable the wakeup set */
+ writel_relaxed(gic->wakeup_irqs[i],
+ base + GIC_DIST_ENABLE_SET + i * 4);
+ }
+ mb();
+ return 0;
+}
+
+static int gic_suspend(void)
+{
+ int i;
+ for (i = 0; i < MAX_GIC_NR; i++)
+ gic_suspend_one(&gic_data[i]);
+ return 0;
+}
+
+extern int msm_show_resume_irq_mask;
+
+static void gic_show_resume_irq(struct gic_chip_data *gic)
+{
+ unsigned int i;
+ u32 enabled;
+ unsigned long pending[32];
+ void __iomem *base = gic->dist_base;
+
+ if (!msm_show_resume_irq_mask)
+ return;
+
+ spin_lock(&irq_controller_lock);
+ for (i = 0; i * 32 < gic->max_irq; i++) {
+ enabled = readl_relaxed(base + GIC_DIST_ENABLE_CLEAR + i * 4);
+ pending[i] = readl_relaxed(base + GIC_DIST_PENDING_SET + i * 4);
+ pending[i] &= enabled;
+ }
+ spin_unlock(&irq_controller_lock);
+
+ for (i = find_first_bit(pending, gic->max_irq);
+ i < gic->max_irq;
+ i = find_next_bit(pending, gic->max_irq, i+1)) {
+ pr_warning("%s: %d triggered", __func__,
+ i + gic->irq_offset);
+ }
+}
+
+static void gic_resume_one(struct gic_chip_data *gic)
+{
+ unsigned int i;
+ void __iomem *base = gic->dist_base;
+
+ gic_show_resume_irq(gic);
+ for (i = 0; i * 32 < gic->max_irq; i++) {
+ /* disable all of them */
+ writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4);
+ /* enable the enabled set */
+ writel_relaxed(gic->enabled_irqs[i],
+ base + GIC_DIST_ENABLE_SET + i * 4);
+ }
+ mb();
+}
+
+static void gic_resume(void)
+{
+ int i;
+ for (i = 0; i < MAX_GIC_NR; i++)
+ gic_resume_one(&gic_data[i]);
+}
+
+static struct syscore_ops gic_syscore_ops = {
+ .suspend = gic_suspend,
+ .resume = gic_resume,
+};
+
+static int __init gic_init_sys(void)
+{
+ register_syscore_ops(&gic_syscore_ops);
+ return 0;
+}
+arch_initcall(gic_init_sys);
+
+#endif
+
static void gic_eoi_irq(struct irq_data *d)
{
if (gic_arch_extn.irq_eoi) {
@@ -202,6 +309,20 @@
static int gic_set_wake(struct irq_data *d, unsigned int on)
{
int ret = -ENXIO;
+ unsigned int reg_offset, bit_offset;
+ unsigned int gicirq = gic_irq(d);
+ struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d);
+
+ /* per-cpu interrupts cannot be wakeup interrupts */
+ WARN_ON(gicirq < 32);
+
+ reg_offset = gicirq / 32;
+ bit_offset = gicirq % 32;
+
+ if (on)
+ gic_data->wakeup_irqs[reg_offset] |= 1 << bit_offset;
+ else
+ gic_data->wakeup_irqs[reg_offset] &= ~(1 << bit_offset);
if (gic_arch_extn.irq_set_wake)
ret = gic_arch_extn.irq_set_wake(d, on);
@@ -250,6 +371,7 @@
#ifdef CONFIG_SMP
.irq_set_affinity = gic_set_affinity,
#endif
+ .irq_disable = gic_disable_irq,
.irq_set_wake = gic_set_wake,
};
@@ -324,7 +446,10 @@
set_irq_flags(i, IRQF_VALID | IRQF_PROBE);
}
+ gic->max_irq = gic_irqs;
+
writel_relaxed(1, base + GIC_DIST_CTRL);
+ mb();
}
static void __cpuinit gic_cpu_init(struct gic_chip_data *gic)
@@ -348,6 +473,7 @@
writel_relaxed(0xf0, base + GIC_CPU_PRIMASK);
writel_relaxed(1, base + GIC_CPU_CTRL);
+ mb();
}
void __init gic_init(unsigned int gic_nr, unsigned int irq_start,
@@ -399,5 +525,47 @@
/* this always happens on GIC0 */
writel_relaxed(map << 16 | irq, gic_data[0].dist_base + GIC_DIST_SOFTINT);
+ mb();
}
#endif
+
+/* before calling this function the interrupts should be disabled
+ * and the irq must be disabled at gic to avoid spurious interrupts */
+bool gic_is_spi_pending(unsigned int irq)
+{
+ struct irq_data *d = irq_get_irq_data(irq);
+ struct gic_chip_data *gic_data = &gic_data[0];
+ u32 mask, val;
+
+ WARN_ON(!irqs_disabled());
+ spin_lock(&irq_controller_lock);
+ mask = 1 << (gic_irq(d) % 32);
+ val = readl(gic_dist_base(d) +
+ GIC_DIST_ENABLE_SET + (gic_irq(d) / 32) * 4);
+ /* warn if the interrupt is enabled */
+ WARN_ON(val & mask);
+ val = readl(gic_dist_base(d) +
+ GIC_DIST_PENDING_SET + (gic_irq(d) / 32) * 4);
+ spin_unlock(&irq_controller_lock);
+ return (bool) (val & mask);
+}
+
+/* before calling this function the interrupts should be disabled
+ * and the irq must be disabled at gic to avoid spurious interrupts */
+void gic_clear_spi_pending(unsigned int irq)
+{
+ struct gic_chip_data *gic_data = &gic_data[0];
+ struct irq_data *d = irq_get_irq_data(irq);
+
+ u32 mask, val;
+ WARN_ON(!irqs_disabled());
+ spin_lock(&irq_controller_lock);
+ mask = 1 << (gic_irq(d) % 32);
+ val = readl(gic_dist_base(d) +
+ GIC_DIST_ENABLE_SET + (gic_irq(d) / 32) * 4);
+ /* warn if the interrupt is enabled */
+ WARN_ON(val & mask);
+ writel(mask, gic_dist_base(d) +
+ GIC_DIST_PENDING_CLEAR + (gic_irq(d) / 32) * 4);
+ spin_unlock(&irq_controller_lock);
+}