rockbox/firmware/usb.c
Barry Wardell e63e62b2a1 Detect charger connected on H10. No need to sleep(HZ) when powering off.
More sensible USB support for H10: plugging in puts it into USB charging mode and leaves Rockbox usable, holding 'O' while pluggin in the USB cable will reboot the H10 (continuing to hold 'O' starts it up in UMS mode), holding any other button brings up the USB screen as previously.
Implement system_reboot() for all PP5020 devices.


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11015 a1c6a512-1295-4272-9138-f99709370657
2006-09-20 23:21:59 +00:00

664 lines
17 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Linus Nielsen Feltzing
*
* iPod driver based on code from the ipodlinux project - http://ipodlinux.org
* Adapted for Rockbox in January 2006
* Original file: podzilla/usb.c
* Copyright (C) 2005 Adam Johnston
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "config.h"
#include "cpu.h"
#include "kernel.h"
#include "thread.h"
#include "system.h"
#include "debug.h"
#include "ata.h"
#include "fat.h"
#include "disk.h"
#include "panic.h"
#include "lcd.h"
#include "adc.h"
#include "usb.h"
#include "button.h"
#include "sprintf.h"
#include "string.h"
#include "hwcompat.h"
#ifdef HAVE_MMC
#include "ata_mmc.h"
#endif
#ifdef TARGET_TREE
#include "usb-target.h"
#endif
extern void dbg_ports(void); /* NASTY! defined in apps/ */
#ifdef HAVE_LCD_BITMAP
bool do_screendump_instead_of_usb = false;
void screen_dump(void); /* Nasty again. Defined in apps/ too */
#endif
#define USB_REALLY_BRAVE
#if !defined(SIMULATOR) && !defined(USB_NONE)
/* Messages from usb_tick and thread states */
#define USB_INSERTED 1
#define USB_EXTRACTED 2
#ifdef HAVE_MMC
#define USB_REENABLE 3
#endif
#ifdef HAVE_USB_POWER
#define USB_POWERED 4
#if CONFIG_KEYPAD == RECORDER_PAD
#define USBPOWER_BUTTON BUTTON_F1
#define USBPOWER_BTN_IGNORE BUTTON_ON
#elif CONFIG_KEYPAD == ONDIO_PAD
#define USBPOWER_BUTTON BUTTON_MENU
#define USBPOWER_BTN_IGNORE BUTTON_OFF
#elif (CONFIG_KEYPAD == IPOD_4G_PAD)
#define USBPOWER_BUTTON BUTTON_MENU
#define USBPOWER_BTN_IGNORE BUTTON_PLAY
#elif CONFIG_KEYPAD == IRIVER_H300_PAD
#define USBPOWER_BUTTON BUTTON_REC
#define USBPOWER_BTN_IGNORE BUTTON_ON
#elif CONFIG_KEYPAD == GIGABEAT_PAD
#define USBPOWER_BUTTON BUTTON_MENU
#define USBPOWER_BTN_IGNORE BUTTON_POWER
#elif CONFIG_KEYPAD == IRIVER_H10_PAD
#define USBPOWER_BUTTON BUTTON_NONE
#define USBPOWER_BTN_IGNORE BUTTON_POWER
#endif
#endif /* HAVE_USB_POWER */
/* The ADC tick reads one channel per tick, and we want to check 3 successive
readings on the USB voltage channel. This doesn't apply to the Player, but
debouncing the USB detection port won't hurt us either. */
#define NUM_POLL_READINGS (NUM_ADC_CHANNELS * 3)
static int countdown;
static int usb_state;
#if defined(HAVE_MMC) && !defined(BOOTLOADER)
static int usb_mmc_countdown = 0;
#endif
/* FIXME: The extra 0x800 is consumed by fat_mount() when the fsinfo
needs updating */
#ifndef BOOTLOADER
static long usb_stack[(DEFAULT_STACK_SIZE + 0x800)/sizeof(long)];
static const char usb_thread_name[] = "usb";
#endif
static struct event_queue usb_queue;
static bool last_usb_status;
static bool usb_monitor_enabled;
#ifndef TARGET_TREE
void usb_enable(bool on)
{
#ifdef USB_ENABLE_ONDIOSTYLE
PACR2 &= ~0x04C0; /* use PA3, PA5 as GPIO */
if(on)
{
#ifdef HAVE_MMC
mmc_enable_int_flash_clock(!mmc_detect());
#endif
if (!(read_hw_mask() & MMC_CLOCK_POLARITY))
and_b(~0x20, &PBDRH); /* old circuit needs SCK1 = low while on USB */
or_b(0x20, &PADRL); /* enable USB */
and_b(~0x08, &PADRL); /* assert card detect */
}
else
{
if (!(read_hw_mask() & MMC_CLOCK_POLARITY))
or_b(0x20, &PBDRH); /* reset SCK1 = high for old circuit */
and_b(~0x20, &PADRL); /* disable USB */
or_b(0x08, &PADRL); /* deassert card detect */
}
or_b(0x28, &PAIORL); /* output for USB enable and card detect */
#elif defined(USB_GMINISTYLE)
{
int smsc_ver = smsc_version();
if (on) {
if (smsc_ver < 4) {
P6 &= ~0x04;
P10 &= ~0x20;
smsc_delay();
P6 |= 0x08;
P10 |= 0x20;
smsc_delay();
}
P6 |= 0x10;
} else {
P6 &= ~0x10;
if (smsc_ver < 4) {
P6 &= ~0x04;
P10 &= ~0x20;
}
}
}
#elif defined(USB_IRIVERSTYLE)
if(on)
{
/* Power on the Cypress chip */
#ifdef IRIVER_H100_SERIES
or_l(0x01000040, &GPIO_OUT);
#else
and_l(~0x00000008,&GPIO1_OUT);
#endif
sleep(2);
}
else
{
/* Power off the Cypress chip */
#ifdef IRIVER_H100_SERIES
and_l(~0x01000040, &GPIO_OUT);
#else
or_l(0x00000008,&GPIO1_OUT);
#endif
}
#elif defined(USB_IPODSTYLE)
/* For the ipod, we can only do one thing with USB mode - reboot
into Apple's flash-based disk-mode. This does not return. */
if (on)
{
/* The following code is copied from ipodlinux */
#if defined(IPOD_COLOR) || defined(IPOD_3G) || \
defined(IPOD_4G) || defined(IPOD_MINI)
unsigned char* storage_ptr = (unsigned char *)0x40017F00;
#elif defined(IPOD_NANO) || defined(IPOD_VIDEO) || defined(IPOD_MINI2G)
unsigned char* storage_ptr = (unsigned char *)0x4001FF00;
#endif
memcpy(storage_ptr, "diskmode\0\0hotstuff\0\0\1", 21);
DEV_RS |= 4; /* Reboot */
}
#elif defined(USB_ISP1582)
/* TODO: Implement USB_ISP1582 */
(void) on;
#elif defined(USB_X5STYLE)
/* TODO X5 */
#elif defined(USB_GIGABEAT_STYLE)
/* TODO gigabeat */
#else
#ifdef HAVE_LCD_BITMAP
if(read_hw_mask() & USB_ACTIVE_HIGH)
on = !on;
#endif
if(on)
{
and_b(~0x04, &PADRH); /* enable USB */
}
else
{
or_b(0x04, &PADRH);
}
or_b(0x04, &PAIORH);
#endif
}
#endif
#ifndef BOOTLOADER
static void usb_slave_mode(bool on)
{
int rc;
if(on)
{
DEBUGF("Entering USB slave mode\n");
ata_soft_reset();
ata_init();
ata_enable(false);
usb_enable(true);
}
else
{
DEBUGF("Leaving USB slave mode\n");
/* Let the ISDx00 settle */
sleep(HZ*1);
usb_enable(false);
rc = ata_init();
if(rc)
{
/* fixme: can we remove this? (already such in main.c) */
char str[32];
lcd_clear_display();
snprintf(str, 31, "ATA error: %d", rc);
lcd_puts(0, 0, str);
lcd_puts(0, 1, "Press ON to debug");
lcd_update();
while(!(button_get(true) & BUTTON_REL)) {};
dbg_ports();
panicf("ata: %d",rc);
}
rc = disk_mount_all();
if (rc <= 0) /* no partition */
panicf("mount: %d",rc);
}
}
static void usb_thread(void)
{
int num_acks_to_expect = -1;
bool waiting_for_ack;
struct event ev;
waiting_for_ack = false;
while(1)
{
queue_wait(&usb_queue, &ev);
switch(ev.id)
{
case USB_INSERTED:
#ifdef HAVE_LCD_BITMAP
if(do_screendump_instead_of_usb)
{
screen_dump();
}
else
#endif
#ifdef HAVE_USB_POWER
if((button_status() & ~USBPOWER_BTN_IGNORE) == USBPOWER_BUTTON)
{
usb_state = USB_POWERED;
}
else
#endif
{
/* Tell all threads that they have to back off the ATA.
We subtract one for our own thread. */
num_acks_to_expect =
queue_broadcast(SYS_USB_CONNECTED, NULL) - 1;
waiting_for_ack = true;
DEBUGF("USB inserted. Waiting for ack from %d threads...\n",
num_acks_to_expect);
}
break;
case SYS_USB_CONNECTED_ACK:
if(waiting_for_ack)
{
num_acks_to_expect--;
if(num_acks_to_expect == 0)
{
DEBUGF("All threads have acknowledged the connect.\n");
#ifdef USB_REALLY_BRAVE
usb_slave_mode(true);
usb_state = USB_INSERTED;
cpu_idle_mode(true);
#else
system_reboot();
#endif
}
else
{
DEBUGF("usb: got ack, %d to go...\n",
num_acks_to_expect);
}
}
break;
case USB_EXTRACTED:
#ifdef HAVE_LCD_BITMAP
if(do_screendump_instead_of_usb)
break;
#endif
#ifdef HAVE_USB_POWER
if(usb_state == USB_POWERED)
{
usb_state = USB_EXTRACTED;
break;
}
#endif
if(usb_state == USB_INSERTED)
{
/* Only disable the USB mode if we really have enabled it
some threads might not have acknowledged the
insertion */
usb_slave_mode(false);
cpu_idle_mode(false);
}
usb_state = USB_EXTRACTED;
/* Tell all threads that we are back in business */
num_acks_to_expect =
queue_broadcast(SYS_USB_DISCONNECTED, NULL) - 1;
waiting_for_ack = true;
DEBUGF("USB extracted. Waiting for ack from %d threads...\n",
num_acks_to_expect);
#ifdef HAVE_LCD_CHARCELLS
lcd_icon(ICON_USB, false);
#endif
break;
case SYS_USB_DISCONNECTED_ACK:
if(waiting_for_ack)
{
num_acks_to_expect--;
if(num_acks_to_expect == 0)
{
DEBUGF("All threads have acknowledged. "
"We're in business.\n");
}
else
{
DEBUGF("usb: got ack, %d to go...\n",
num_acks_to_expect);
}
}
break;
#ifdef HAVE_MMC
case SYS_MMC_INSERTED:
case SYS_MMC_EXTRACTED:
if(usb_state == USB_INSERTED)
{
usb_enable(false);
usb_mmc_countdown = HZ/2; /* re-enable after 0.5 sec */
}
break;
case USB_REENABLE:
if(usb_state == USB_INSERTED)
usb_enable(true); /* reenable only if still inserted */
break;
#endif
}
}
}
#endif
#ifndef TARGET_TREE
bool usb_detect(void)
{
bool current_status;
#ifdef USB_RECORDERSTYLE
current_status = (adc_read(ADC_USB_POWER) > 500)?true:false;
#endif
#ifdef USB_FMRECORDERSTYLE
current_status = (adc_read(ADC_USB_POWER) <= 512)?true:false;
#endif
#ifdef USB_PLAYERSTYLE
current_status = (PADR & 0x8000)?false:true;
#endif
#ifdef USB_IRIVERSTYLE
current_status = (GPIO1_READ & 0x80)?true:false;
#endif
#ifdef USB_GMINISTYLE
current_status = (P5 & 0x10)?true:false;
#endif
#ifdef USB_IPODSTYLE
/* The following check is in the ipodlinux source, with the
comment "USB2D_IDENT is bad" if USB2D_IDENT != 0x22FA05 */
if (USB2D_IDENT != 0x22FA05) {
return false;
}
current_status = (USB_STATUS & 0x800)?true:false;
#endif
#ifdef USB_ISP1582
/* TODO: Implement USB_ISP1582 */
current_status = false;
#endif
return current_status;
}
#endif
#ifndef BOOTLOADER
static void usb_tick(void)
{
bool current_status;
#ifdef USB_GMINISTYLE
/* Keep usb chip in usb state (?) */
if (P5 & 0x10) {
if ((P10 & 0x20) == 0 || (P6 & 0x08) == 0) {
if (smsc_version() < 4) {
P6 |= 0x08;
P10 |= 0x20;
}
}
}
#endif
if(usb_monitor_enabled)
{
current_status = usb_detect();
/* Only report when the status has changed */
if(current_status != last_usb_status)
{
last_usb_status = current_status;
countdown = NUM_POLL_READINGS;
}
else
{
/* Count down until it gets negative */
if(countdown >= 0)
countdown--;
/* Report to the thread if we have had 3 identical status
readings in a row */
if(countdown == 0)
{
if(current_status)
queue_post(&usb_queue, USB_INSERTED, NULL);
else
queue_post(&usb_queue, USB_EXTRACTED, NULL);
}
}
}
#ifdef HAVE_MMC
if(usb_mmc_countdown > 0)
{
usb_mmc_countdown--;
if (usb_mmc_countdown == 0)
queue_post(&usb_queue, USB_REENABLE, NULL);
}
#endif
}
#endif
void usb_acknowledge(long id)
{
queue_post(&usb_queue, id, NULL);
}
void usb_init(void)
{
usb_state = USB_EXTRACTED;
usb_monitor_enabled = false;
countdown = -1;
#ifdef USB_IRIVERSTYLE
or_l(0x00000080, &GPIO1_FUNCTION); /* GPIO39 is the USB detect input */
#ifdef IRIVER_H300_SERIES
/* ISD300 3.3V ON */
or_l(8,&GPIO1_FUNCTION);
or_l(8,&GPIO1_OUT);
or_l(8,&GPIO1_ENABLE);
/* Tristate the SCK/SDA to the ISD300 config EEPROM */
and_l(~0x03000000, &GPIO_ENABLE);
or_l(0x03000000, &GPIO_FUNCTION);
#else
and_l(~0x01000040, &GPIO_OUT); /* GPIO24 is the Cypress chip power */
or_l(0x01000040, &GPIO_ENABLE);
or_l(0x01000040, &GPIO_FUNCTION);
#endif
#elif defined(USB_IPODSTYLE)
int r0;
outl(inl(0x70000084) | 0x200, 0x70000084);
outl(inl(0x7000002C) | 0x3000000, 0x7000002C);
outl(inl(0x6000600C) | 0x400000, 0x6000600C);
outl(inl(0x60006004) | 0x400000, 0x60006004); /* reset usb start */
outl(inl(0x60006004) & ~0x400000, 0x60006004); /* reset usb end */
outl(inl(0x70000020) | 0x80000000, 0x70000020);
while ((inl(0x70000028) & 0x80) == 0);
outl(inl(0xc5000184) | 0x100, 0xc5000184);
while ((inl(0xc5000184) & 0x100) != 0);
outl(inl(0xc50001A4) | 0x5F000000, 0xc50001A4);
if ((inl(0xc50001A4) & 0x100) == 0) {
outl(inl(0xc50001A8) & ~0x3, 0xc50001A8);
outl(inl(0xc50001A8) | 0x2, 0xc50001A8);
outl(inl(0x70000028) | 0x4000, 0x70000028);
outl(inl(0x70000028) | 0x2, 0x70000028);
} else {
outl(inl(0xc50001A8) | 0x3, 0xc50001A8);
outl(inl(0x70000028) &~0x4000, 0x70000028);
outl(inl(0x70000028) | 0x2, 0x70000028);
}
outl(inl(0xc5000140) | 0x2, 0xc5000140);
while((inl(0xc5000140) & 0x2) != 0);
r0 = inl(0xc5000184);
/* Note from IPL source (referring to next 5 lines of code:
THIS NEEDS TO BE CHANGED ONCE THERE IS KERNEL USB */
outl(inl(0x70000020) | 0x80000000, 0x70000020);
outl(inl(0x6000600C) | 0x400000, 0x6000600C);
while ((inl(0x70000028) & 0x80) == 0);
outl(inl(0x70000028) | 0x2, 0x70000028);
udelay(0x186A0);
#endif
usb_enable(false);
/* We assume that the USB cable is extracted */
last_usb_status = false;
#ifndef BOOTLOADER
queue_init(&usb_queue, true);
create_thread(usb_thread, usb_stack, sizeof(usb_stack),
usb_thread_name IF_PRIO(, PRIORITY_SYSTEM));
tick_add_task(usb_tick);
#endif
}
void usb_wait_for_disconnect(struct event_queue *q)
{
struct event ev;
/* Don't return until we get SYS_USB_DISCONNECTED */
while(1)
{
queue_wait(q, &ev);
if(ev.id == SYS_USB_DISCONNECTED)
{
usb_acknowledge(SYS_USB_DISCONNECTED_ACK);
return;
}
}
}
int usb_wait_for_disconnect_w_tmo(struct event_queue *q, int ticks)
{
struct event ev;
/* Don't return until we get SYS_USB_DISCONNECTED or SYS_TIMEOUT */
while(1)
{
queue_wait_w_tmo(q, &ev, ticks);
switch(ev.id)
{
case SYS_USB_DISCONNECTED:
usb_acknowledge(SYS_USB_DISCONNECTED_ACK);
return 0;
break;
case SYS_TIMEOUT:
return 1;
break;
}
}
}
void usb_start_monitoring(void)
{
usb_monitor_enabled = true;
}
bool usb_inserted(void)
{
#ifdef HAVE_USB_POWER
return usb_state == USB_INSERTED || usb_state == USB_POWERED;
#else
return usb_state == USB_INSERTED;
#endif
}
#ifdef HAVE_USB_POWER
bool usb_powered(void)
{
return usb_state == USB_POWERED;
}
#endif
#else
#ifdef USB_NONE
bool usb_inserted(void)
{
return false;
}
#endif
/* Dummy simulator functions */
void usb_acknowledge(long id)
{
id = id;
}
void usb_init(void)
{
}
void usb_start_monitoring(void)
{
}
bool usb_detect(void)
{
return false;
}
void usb_wait_for_disconnect(struct event_queue *q)
{
(void)q;
}
#endif /* USB_NONE or SIMULATOR */