416 lines
11 KiB
C
416 lines
11 KiB
C
// ===========================================================================
|
|
// Cybook Input Output - cyio.c
|
|
// Copyright (C) 2008-2010 Bookeen - All rights reserved
|
|
// ===========================================================================
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/proc_fs.h>
|
|
|
|
#include <asm/arch/regs-gpio.h>
|
|
#include <asm/arch/regs-gpioj.h>
|
|
|
|
#include <cybook.h>
|
|
|
|
#include <linux/cyevent.h>
|
|
|
|
//#define DEBUG_MESSAGES
|
|
//#define DBG_IRQ
|
|
|
|
#include <asm/hardware.h>
|
|
#include <asm/delay.h>
|
|
#include <asm/uaccess.h>
|
|
#include <asm-arm/irq.h>
|
|
#include <asm/arch/gpio.h>
|
|
#include <asm/arch/regs-gpio.h>
|
|
#include <asm/arch/regs-irq.h>
|
|
#include <asm-arm/arch-s3c2410/irqs.h>
|
|
#include <asm-arm/arch-s3c2410/gpio.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include "asm/io.h"
|
|
// ===========================================================================
|
|
#define IO_TIMER_DELAY_0500 ((HZ * 1) / 2) /* 500 ms */
|
|
#define IO_TIMER_DELAY_2000 ((HZ * 2) / 1) /* 2 s */
|
|
#define IO_TIMER_DELAY_1000 ((HZ * 1) / 1) /* 1 s */
|
|
|
|
static struct timer_list io_timer;
|
|
|
|
#define TRUE (1==1)
|
|
#define FALSE (0==1)
|
|
|
|
#define PFX "CyIO:"
|
|
|
|
typedef struct _cyIrq_
|
|
{
|
|
int nIrq;
|
|
u32 nGpio;
|
|
u8 bActive;
|
|
u8 bAllowRepeat; // Allow Repeat?
|
|
u8 nCodeActive; // All interrupts generate a code when active
|
|
u8 nCodeInactive; // Some interrupts generate a code when inactive
|
|
char* sName;
|
|
} cyIrq;
|
|
|
|
#define GPIO_F0 S3C2410_GPF0
|
|
#define GPIO_F1 S3C2410_GPF1
|
|
#define GPIO_F2 S3C2410_GPF2
|
|
#define GPIO_F3 S3C2410_GPF3
|
|
#define GPIO_F4 S3C2410_GPF4
|
|
#define GPIO_F5 S3C2410_GPF5
|
|
#define GPIO_F6 S3C2410_GPF6
|
|
#define GPIO_F7 S3C2410_GPF7
|
|
#define GPIO_F8 S3C2410_GPF8
|
|
#define GPIO_F9 S3C2410_GPF9
|
|
#define GPIO_F10 S3C2410_GPF10
|
|
|
|
#define GPIO_G0 S3C2410_GPG0
|
|
#define GPIO_G1 S3C2410_GPG1
|
|
#define GPIO_G2 S3C2410_GPG2
|
|
#define GPIO_G3 S3C2410_GPG3
|
|
#define GPIO_G4 S3C2410_GPG4
|
|
#define GPIO_G5 S3C2410_GPG5
|
|
#define GPIO_G6 S3C2410_GPG6
|
|
#define GPIO_G7 S3C2410_GPG7
|
|
#define GPIO_G8 S3C2410_GPG8
|
|
#define GPIO_G9 S3C2410_GPG9
|
|
|
|
|
|
#define GPIO_D10 S3C2410_GPD10
|
|
#define GPIO_D11 S3C2410_GPD11
|
|
#define GPIO_D14 S3C2410_GPD14
|
|
|
|
#define GPIO_H4 S3C2410_GPH4
|
|
#define GPIO_H5 S3C2410_GPH5
|
|
|
|
/* USB related */
|
|
#define USBDETECT_GPIO GPIO_G1
|
|
#define USBDETECT_EINT IRQ_EINT9
|
|
|
|
static int nCnt;
|
|
static cyIrq s_nIrq[] ={
|
|
/* IRQ GPIO A B Depress Event Release Event Name Number */
|
|
{ IRQ_EINT0, GPIO_F0, 0, 1, CYEVENT_KEY_OFF, 0, "PowerBtn" } //0
|
|
, { IRQ_EINT7, GPIO_F7, 0, 1, CYEVENT_KEY_DOWN, 0, "Down" } //1
|
|
, { IRQ_EINT15, GPIO_G7, 0, 1, CYEVENT_KEY_UP, 0, "Up" } //2
|
|
, { IRQ_EINT8, GPIO_G0, 0, 1, CYEVENT_KEY_LEFT, 0, "Left" } //3
|
|
, { IRQ_EINT10, GPIO_G2, 0, 1, CYEVENT_KEY_RIGHT, 0, "Right" } //4
|
|
, { IRQ_EINT14, GPIO_G6, 0, 1, CYEVENT_KEY_ENTER, 0, "Center" } //5
|
|
/* A:Reserved (must be 0) - B: Allow Repeat? (1: Yes, 0: No) */
|
|
};
|
|
|
|
static irqreturn_t io_interrupt (int irq, void *dev_id);
|
|
static irqreturn_t io_usb_interrupt (int irq, void *dev_id);
|
|
|
|
static void io_timer_handler (unsigned long nData);
|
|
|
|
static void io_ev_emptylist(void);
|
|
static int io_ioctl(unsigned int cmd, unsigned long arg);
|
|
static void io_ev_read(unsigned char data);
|
|
|
|
static struct cyevent_device cyio_cye_dev =
|
|
{
|
|
.deepsleep = NULL,
|
|
.suspend = NULL,
|
|
.resume = NULL,
|
|
.event_read = io_ev_read,
|
|
.ioctl = io_ioctl,
|
|
.ioctl_prefix = IOCTL_PFX('C'),
|
|
.event_listempty = io_ev_emptylist,
|
|
.event_read_listen = 0,
|
|
};
|
|
|
|
#undef MSG
|
|
#undef DBG
|
|
#ifdef DEBUG_MESSAGES
|
|
# define MSG(str) { printk(KERN_ERR str "\n"); }
|
|
# define DBG(str, ...) { printk(KERN_ERR str "\n", __VA_ARGS__); }
|
|
#else
|
|
# define MSG(str)
|
|
# define DBG(str, ...)
|
|
#endif
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void io_initIrq (void)
|
|
{
|
|
int i;
|
|
cyIrq *pIrq, *pIrq0;
|
|
int ret;
|
|
|
|
pIrq0 = &s_nIrq[0];
|
|
|
|
|
|
DBG(">>%s()\n", __func__);
|
|
|
|
for ( i = 0, pIrq = pIrq0; i < nCnt; ++i, ++pIrq )
|
|
{
|
|
int nIrq = pIrq->nIrq;
|
|
set_irq_type(nIrq, IRQT_BOTHEDGE);
|
|
|
|
/* Set no Pullup and no Pulldown */
|
|
s3c2410_gpio_pullup(pIrq->nGpio, 0);
|
|
|
|
ret = request_irq(nIrq, io_interrupt, SA_INTERRUPT | SA_SHIRQ, pIrq->sName, pIrq);
|
|
enable_irq_wake(nIrq);
|
|
if ( ret != 0 )
|
|
{
|
|
printk(KERN_ERR PFX "Error registering IRQ %d [%s]!\n", nIrq, pIrq->sName);
|
|
}
|
|
}
|
|
|
|
/* Setup USB irq */
|
|
set_irq_type(USBDETECT_EINT, IRQT_BOTHEDGE);
|
|
s3c2410_gpio_pullup(USBDETECT_GPIO, 0);
|
|
ret = request_irq(USBDETECT_EINT, io_usb_interrupt, SA_INTERRUPT | SA_SHIRQ, "USB Detect", &nCnt);
|
|
enable_irq_wake(USBDETECT_EINT);
|
|
if (ret != 0)
|
|
{
|
|
printk(KERN_ERR PFX "Error registering USB Detect IRQ!\n");
|
|
}
|
|
|
|
init_timer(&io_timer);
|
|
io_timer.function = io_timer_handler;
|
|
io_timer.data = CYEVENT_TIMER_SCREEN;
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
static void io_timer_handler (unsigned long nData)
|
|
{
|
|
CyEvent_t event = NEW_CYEVENT(CYEVENT_TYPE_TIMER);
|
|
|
|
DBG("Timer [%ld] tick...\n", nData);
|
|
|
|
del_timer(&io_timer);
|
|
event.data.timer_type = nData;
|
|
|
|
CyEvent_PushNewUniqueEvent(&event);
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
void io_deinitIrq (void)
|
|
{
|
|
int i;
|
|
cyIrq *pIrq, *pIrq0;
|
|
|
|
del_timer(&io_timer);
|
|
|
|
pIrq0 = &s_nIrq[0];
|
|
for ( i = 0, pIrq = pIrq0; i < nCnt; ++i, ++pIrq )
|
|
{
|
|
disable_irq_wake(pIrq->nIrq);
|
|
free_irq(pIrq->nIrq, pIrq);
|
|
}
|
|
|
|
disable_irq_wake(USBDETECT_EINT);
|
|
free_irq(USBDETECT_EINT, &nCnt);
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
static int is_usb_plugged(void)
|
|
{
|
|
int i, nUp = 0;
|
|
|
|
for ( i = 0; i < 5000; ++i )
|
|
if ( gpio_get_value(USBDETECT_GPIO) )
|
|
++nUp;
|
|
return nUp > 2500;
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
static void io_ev_emptylist(void)
|
|
{
|
|
/* If we are powered, don't use timers... */
|
|
if (!is_usb_plugged())
|
|
{
|
|
del_timer(&io_timer);
|
|
if (io_timer.data == CYEVENT_TIMER_SCREEN)
|
|
{
|
|
io_timer.expires = IO_TIMER_DELAY_0500;
|
|
io_timer.expires += jiffies;
|
|
add_timer(&io_timer);
|
|
}
|
|
else //if (io_timer.data == CYEVENT_TIMER_DEVICE)
|
|
{
|
|
io_timer.expires = IO_TIMER_DELAY_2000 - IO_TIMER_DELAY_0500;
|
|
io_timer.expires += jiffies;
|
|
add_timer(&io_timer);
|
|
}
|
|
}
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
static void io_ev_read(unsigned char type)
|
|
{
|
|
del_timer(&io_timer);
|
|
|
|
if (type != CYEVENT_TYPE_TIMER)
|
|
io_timer.data = CYEVENT_TIMER_SCREEN;
|
|
else
|
|
io_timer.data = (io_timer.data == CYEVENT_TIMER_SCREEN)?CYEVENT_TIMER_DEVICE:CYEVENT_TIMER_SCREEN;
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
static int io_ioctl(unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret = -EINVAL;
|
|
unsigned long value;
|
|
int i;
|
|
ret = -EINVAL;
|
|
|
|
switch(cmd)
|
|
{
|
|
case CYIO_CTL_LED_CMD:
|
|
|
|
if ((arg & 0x2) && (arg & 0x1)) /* Power LED */
|
|
{
|
|
//GPH12
|
|
__raw_writel(__raw_readl(S3C2410_GPHDAT) | (1 << 12), S3C2410_GPHDAT);
|
|
}
|
|
else if (arg & 0x2)
|
|
{
|
|
__raw_writel(__raw_readl(S3C2410_GPHDAT) & ~(1 << 12), S3C2410_GPHDAT);
|
|
}
|
|
|
|
if ((arg & 0x20) && (arg & 0x10)) /* Wifi LED */
|
|
{
|
|
//GPK8
|
|
__raw_writel(__raw_readl(S3C2416_GPKDAT) | (1 << 8), S3C2416_GPKDAT);
|
|
}
|
|
else if (arg & 0x20)
|
|
{
|
|
__raw_writel(__raw_readl(S3C2416_GPKDAT) & ~(1 << 8), S3C2416_GPKDAT);
|
|
}
|
|
|
|
if ((arg & 0x200) && (arg & 0x100)) /* Bluetooth LED */
|
|
{
|
|
//GPK9
|
|
__raw_writel(__raw_readl(S3C2416_GPKDAT) | (1 << 9), S3C2416_GPKDAT);
|
|
}
|
|
else if (arg & 0x200)
|
|
{
|
|
__raw_writel(__raw_readl(S3C2416_GPKDAT) & ~(1 << 9), S3C2416_GPKDAT);
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
case CYIO_CTL_USB_STATUS:
|
|
value = gpio_get_value(S3C2410_GPG1);
|
|
|
|
put_user(value, (unsigned long __user *)arg);
|
|
ret = 0;
|
|
break;
|
|
|
|
case CYIO_CTL_SD_STATUS:
|
|
value = gpio_get_value(S3C2410_GPF1);
|
|
|
|
put_user(value, (unsigned long __user *)arg);
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
static irqreturn_t io_usb_interrupt (int irq, void *dev_id)
|
|
{
|
|
CyEvent_t event = NEW_CYEVENT(CYEVENT_TYPE_SYSTEM);
|
|
|
|
|
|
event.flags |= CYEVENT_FLAG_USB_MESSAGE;
|
|
|
|
if (is_usb_plugged())
|
|
{
|
|
event.flags |= CYEVENT_FLAG_SYS_STATUS;
|
|
}
|
|
|
|
CyEvent_PushNewEvent(&event, false);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
static irqreturn_t io_interrupt (int irq, void *dev_id)
|
|
{
|
|
u8 bActive;
|
|
u8 blCodeActive;
|
|
unsigned long flags;
|
|
CyEvent_t event = NEW_CYEVENT(CYEVENT_TYPE_KEY);
|
|
cyIrq* pIrq = (cyIrq*)dev_id;
|
|
|
|
spinlock_t lock = SPIN_LOCK_UNLOCKED;
|
|
|
|
#ifdef DBG_IRQ
|
|
static int s_nIrq_dbg = 0;
|
|
++s_nIrq_dbg;
|
|
#endif
|
|
|
|
/* Prevent bouncing... */
|
|
spin_lock_irqsave(&lock, flags);
|
|
{ // Avoid bounces
|
|
int i, nUp = 0;
|
|
for ( i = 0; i < 10000; ++i )
|
|
if ( gpio_get_value(pIrq->nGpio) )
|
|
++nUp;
|
|
bActive = (nUp < 5000);
|
|
}
|
|
spin_unlock_irqrestore(&lock, flags);
|
|
|
|
if ( pIrq->bActive == bActive )
|
|
return IRQ_HANDLED;
|
|
|
|
pIrq->bActive = bActive;
|
|
|
|
#ifdef DBG_IRQ
|
|
DBG(".. io_irq #%d [%s][%c] bActive[%d]", s_nIrq_dbg, pIrq->sName, bActive || !pIrq->nCodeInactive ? (char)pIrq->nCodeActive : (char)pIrq->nCodeInactive, (bActive ? pIrq->nCodeActive : pIrq->nCodeInactive), bActive);
|
|
#endif
|
|
|
|
blCodeActive = bActive ? pIrq->nCodeActive : pIrq->nCodeInactive;
|
|
|
|
event.data.key = blCodeActive;
|
|
event.flags |= CYEVENT_FLAG_KEY_CONTROL_CHARS;
|
|
|
|
DBG("blCodeActive: %d", blCodeActive);
|
|
if ( blCodeActive == 0 )
|
|
{
|
|
CyEvent_InvalidateRepeatableEvent(&event);
|
|
}
|
|
else
|
|
{
|
|
if ( pIrq->bAllowRepeat )
|
|
CyEvent_PushNewEvent(&event, true);
|
|
else
|
|
CyEvent_PushNewUniqueEvent(&event);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
// ===========================================================================
|
|
static int __init cyIo_init (void)
|
|
{
|
|
nCnt = sizeof (s_nIrq) / sizeof (s_nIrq[0]);
|
|
|
|
io_initIrq();
|
|
CyEvent_RegisterDevice(&cyio_cye_dev);
|
|
return 0;
|
|
}
|
|
// ---------------------------------------------------------------------------
|
|
static void __exit cyIo_exit (void)
|
|
{
|
|
CyEvent_DeregisterDevice(&cyio_cye_dev);
|
|
io_deinitIrq();
|
|
//MSG("<< cyIo_exit");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
module_init (cyIo_init);
|
|
module_exit (cyIo_exit);
|
|
// ---------------------------------------------------------------------------
|
|
MODULE_LICENSE ("BPL (Bookeen Private Licence)");
|
|
MODULE_AUTHOR ("Bookeen <developers@bookeen.com>");
|
|
MODULE_DESCRIPTION ("Cybook IO Manager");
|
|
MODULE_VERSION ("3.0");
|
|
// ===========================================================================
|