arch/arm/Kconfig | 13 +- arch/arm/configs/em_x270_defconfig | 367 +++-- arch/arm/mach-pxa/Kconfig | 8 + arch/arm/mach-pxa/Makefile | 9 +- arch/arm/mach-pxa/cpu-pxa.c | 442 ++++++ arch/arm/mach-pxa/em-x270-devices.c | 331 +++++ arch/arm/mach-pxa/em-x270-lcd.c | 223 +++ arch/arm/mach-pxa/em-x270-pm.c | 892 ++++++++++++ arch/arm/mach-pxa/em-x270.c | 127 ++- arch/arm/mach-pxa/pwr-i2c.c | 539 +++++++ arch/arm/mach-pxa/pxa27x.c | 6 +- arch/arm/mach-pxa/spitz.c | 27 + drivers/i2c/chips/Kconfig | 13 + drivers/i2c/chips/Makefile | 1 + drivers/i2c/chips/da9030.c | 1213 ++++++++++++++++ drivers/i2c/chips/da9030.h | 282 ++++ drivers/input/touchscreen/Kconfig | 43 + drivers/input/touchscreen/Makefile | 14 + drivers/input/touchscreen/wm9705.c | 360 +++++ drivers/input/touchscreen/wm9712.c | 464 ++++++ drivers/input/touchscreen/wm9713.c | 461 ++++++ drivers/input/touchscreen/wm97xx-core.c | 859 +++++++++++ drivers/leds/Kconfig | 6 + drivers/leds/Makefile | 1 + drivers/leds/leds-em-x270.c | 99 ++ drivers/mtd/chips/jedec_probe.c | 58 +- drivers/net/dm9000.c | 1 + drivers/power/Kconfig | 6 + drivers/power/Makefile | 1 + drivers/power/em_x270_battery.c | 579 ++++++++ drivers/usb/gadget/Kconfig | 20 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/epautoconf.c | 9 +- drivers/usb/gadget/ether.c | 63 +- drivers/usb/gadget/file_storage.c | 11 +- drivers/usb/gadget/pxa27x_udc.c | 2387 +++++++++++++++++++++++++++++++ drivers/usb/gadget/pxa27x_udc.h | 298 ++++ drivers/usb/gadget/pxa2xx_udc.h | 7 +- drivers/usb/gadget/serial.c | 18 +- drivers/usb/gadget/zero.c | 13 +- drivers/video/backlight/Kconfig | 2 +- include/asm-arm/arch-pxa/pwr-i2c.h | 61 + include/linux/da9030.h | 118 ++ include/linux/usb_gadget.h | 23 +- include/linux/wm97xx.h | 291 ++++ sound/soc/pxa/Kconfig | 9 + sound/soc/pxa/Makefile | 2 + sound/soc/pxa/em-x270.c | 137 ++ 48 files changed, 10742 insertions(+), 173 deletions(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 691aae3..cf1dbc2 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -858,7 +858,7 @@ config KEXEC endmenu -if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_IMX ) +if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_IMX || ARCH_PXA) menu "CPU Frequency scaling" @@ -894,6 +894,15 @@ config CPU_FREQ_IMX If in doubt, say N. +config CPU_FREQ_PXA + tristate "CPUfreq driver for PXA2xx CPUs" + depends on CPU_FREQ && ARCH_PXA + default y + help + Thes enables the CPUfreq driver for PXA2xx CPUs. + + If in doubt, say Y. + endmenu endif @@ -1029,6 +1038,8 @@ source "drivers/spi/Kconfig" source "drivers/w1/Kconfig" +source "drivers/power/Kconfig" + source "drivers/hwmon/Kconfig" #source "drivers/l3/Kconfig" diff --git a/arch/arm/configs/em_x270_defconfig b/arch/arm/configs/em_x270_defconfig index 6bea090..3246136 100644 --- a/arch/arm/configs/em_x270_defconfig +++ b/arch/arm/configs/em_x270_defconfig @@ -1,13 +1,13 @@ # # Automatically generated make config: don't edit -# Linux kernel version: 2.6.22 -# Mon Jul 9 15:18:20 2007 +# Linux kernel version: 2.6.23-rc9 +# Tue Oct 9 11:19:21 2007 # CONFIG_ARM=y CONFIG_SYS_SUPPORTS_APM_EMULATION=y CONFIG_GENERIC_GPIO=y CONFIG_GENERIC_TIME=y -# CONFIG_GENERIC_CLOCKEVENTS is not set +CONFIG_GENERIC_CLOCKEVENTS=y CONFIG_MMU=y # CONFIG_NO_IOPORT is not set CONFIG_GENERIC_HARDIRQS=y @@ -27,25 +27,20 @@ CONFIG_VECTORS_BASE=0xffff0000 CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" # -# Code maturity level options +# General setup # CONFIG_EXPERIMENTAL=y CONFIG_BROKEN_ON_SMP=y CONFIG_INIT_ENV_ARG_LIMIT=32 - -# -# General setup -# CONFIG_LOCALVERSION="-em-x270" # CONFIG_LOCALVERSION_AUTO is not set CONFIG_SWAP=y CONFIG_SYSVIPC=y -# CONFIG_IPC_NS is not set CONFIG_SYSVIPC_SYSCTL=y # CONFIG_POSIX_MQUEUE is not set # CONFIG_BSD_PROCESS_ACCT is not set # CONFIG_TASKSTATS is not set -# CONFIG_UTS_NS is not set +# CONFIG_USER_NS is not set # CONFIG_AUDIT is not set CONFIG_IKCONFIG=y CONFIG_IKCONFIG_PROC=y @@ -71,34 +66,27 @@ CONFIG_FUTEX=y CONFIG_ANON_INODES=y CONFIG_EPOLL=y CONFIG_SIGNALFD=y -CONFIG_TIMERFD=y CONFIG_EVENTFD=y CONFIG_SHMEM=y CONFIG_VM_EVENT_COUNTERS=y -CONFIG_SLAB=y -# CONFIG_SLUB is not set +CONFIG_SLUB_DEBUG=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y # CONFIG_SLOB is not set CONFIG_RT_MUTEXES=y # CONFIG_TINY_SHMEM is not set CONFIG_BASE_SMALL=0 - -# -# Loadable module support -# CONFIG_MODULES=y CONFIG_MODULE_UNLOAD=y CONFIG_MODULE_FORCE_UNLOAD=y # CONFIG_MODVERSIONS is not set # CONFIG_MODULE_SRCVERSION_ALL is not set CONFIG_KMOD=y - -# -# Block layer -# CONFIG_BLOCK=y # CONFIG_LBD is not set # CONFIG_BLK_DEV_IO_TRACE is not set # CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set # # IO Schedulers @@ -139,6 +127,7 @@ CONFIG_DEFAULT_IOSCHED="anticipatory" # CONFIG_ARCH_L7200 is not set # CONFIG_ARCH_KS8695 is not set # CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_MXC is not set # CONFIG_ARCH_PNX4008 is not set CONFIG_ARCH_PXA=y # CONFIG_ARCH_RPC is not set @@ -160,6 +149,15 @@ CONFIG_ARCH_PXA=y # CONFIG_MACH_TRIZEPS4 is not set CONFIG_MACH_EM_X270=y CONFIG_PXA27x=y +CONFIG_PXA_PWR_I2C=y + +# +# Boot options +# + +# +# Power management +# # # Processor Type @@ -185,6 +183,7 @@ CONFIG_XSCALE_PMU=y # # Bus support # +# CONFIG_PCI_SYSCALL is not set # CONFIG_ARCH_SUPPORTS_MSI is not set # @@ -196,8 +195,9 @@ CONFIG_XSCALE_PMU=y # Kernel Features # # CONFIG_TICK_ONESHOT is not set +# CONFIG_NO_HZ is not set +# CONFIG_HIGH_RES_TIMERS is not set # CONFIG_PREEMPT is not set -# CONFIG_NO_IDLE_HZ is not set CONFIG_HZ=100 CONFIG_AEABI=y CONFIG_OABI_COMPAT=y @@ -212,6 +212,8 @@ CONFIG_FLAT_NODE_MEM_MAP=y CONFIG_SPLIT_PTLOCK_CPUS=4096 # CONFIG_RESOURCES_64BIT is not set CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y CONFIG_ALIGNMENT_TRAP=y # @@ -219,11 +221,28 @@ CONFIG_ALIGNMENT_TRAP=y # CONFIG_ZBOOT_ROM_TEXT=0x0 CONFIG_ZBOOT_ROM_BSS=0x0 -CONFIG_CMDLINE="" +CONFIG_CMDLINE="root=/dev/mtdblock2 rootfstype=jffs2 console=ttyS0,115200" # CONFIG_XIP_KERNEL is not set # CONFIG_KEXEC is not set # +# CPU Frequency scaling +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_TABLE=y +# CONFIG_CPU_FREQ_DEBUG is not set +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=m +CONFIG_CPU_FREQ_GOV_USERSPACE=m +CONFIG_CPU_FREQ_GOV_ONDEMAND=m +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=m +CONFIG_CPU_FREQ_PXA=y + +# # Floating point emulation # @@ -238,8 +257,8 @@ CONFIG_FPE_NWFPE=y # Userspace binary formats # CONFIG_BINFMT_ELF=y -# CONFIG_BINFMT_AOUT is not set -# CONFIG_BINFMT_MISC is not set +CONFIG_BINFMT_AOUT=m +CONFIG_BINFMT_MISC=m # # Power management options @@ -247,8 +266,10 @@ CONFIG_BINFMT_ELF=y CONFIG_PM=y CONFIG_PM_LEGACY=y # CONFIG_PM_DEBUG is not set -# CONFIG_PM_SYSFS_DEPRECATED is not set -CONFIG_APM_EMULATION=m +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND_UP_POSSIBLE=y +CONFIG_SUSPEND=y +CONFIG_APM_EMULATION=y # # Networking @@ -316,6 +337,7 @@ CONFIG_DEFAULT_TCP_CONG="cubic" # QoS and/or fair queueing # # CONFIG_NET_SCHED is not set +CONFIG_NET_SCH_FIFO=y # # Network testing @@ -350,9 +372,12 @@ CONFIG_BT_HCIBFUSB=m # # Wireless # -# CONFIG_CFG80211 is not set -# CONFIG_WIRELESS_EXT is not set -# CONFIG_MAC80211 is not set +CONFIG_CFG80211=m +CONFIG_WIRELESS_EXT=y +CONFIG_MAC80211=m +# CONFIG_MAC80211_LEDS is not set +CONFIG_MAC80211_DEBUGFS=y +# CONFIG_MAC80211_DEBUG is not set CONFIG_IEEE80211=m # CONFIG_IEEE80211_DEBUG is not set CONFIG_IEEE80211_CRYPT_WEP=m @@ -360,6 +385,7 @@ CONFIG_IEEE80211_CRYPT_CCMP=m # CONFIG_IEEE80211_CRYPT_TKIP is not set # CONFIG_IEEE80211_SOFTMAC is not set # CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set # # Device Drivers @@ -374,10 +400,6 @@ CONFIG_FW_LOADER=y # CONFIG_DEBUG_DRIVER is not set # CONFIG_DEBUG_DEVRES is not set # CONFIG_SYS_HYPERVISOR is not set - -# -# Connector - unified userspace <-> kernelspace linker -# # CONFIG_CONNECTOR is not set CONFIG_MTD=y # CONFIG_MTD_DEBUG is not set @@ -402,11 +424,10 @@ CONFIG_MTD_BLOCK=y # # RAM/ROM/Flash chip drivers # -# CONFIG_MTD_CFI is not set -# CONFIG_MTD_JEDECPROBE is not set -# CONFIG_MTD_CFI_NOSWAP is not set -# CONFIG_MTD_CFI_BE_BYTE_SWAP is not set -# CONFIG_MTD_CFI_LE_BYTE_SWAP is not set +CONFIG_MTD_CFI=y +CONFIG_MTD_JEDECPROBE=y +CONFIG_MTD_GEN_PROBE=y +# CONFIG_MTD_CFI_ADV_OPTIONS is not set CONFIG_MTD_MAP_BANK_WIDTH_1=y CONFIG_MTD_MAP_BANK_WIDTH_2=y CONFIG_MTD_MAP_BANK_WIDTH_4=y @@ -417,14 +438,25 @@ CONFIG_MTD_CFI_I1=y CONFIG_MTD_CFI_I2=y # CONFIG_MTD_CFI_I4 is not set # CONFIG_MTD_CFI_I8 is not set +CONFIG_MTD_CFI_INTELEXT=y +CONFIG_MTD_CFI_AMDSTD=y +CONFIG_MTD_CFI_STAA=y +CONFIG_MTD_CFI_UTIL=y # CONFIG_MTD_RAM is not set # CONFIG_MTD_ROM is not set # CONFIG_MTD_ABSENT is not set +# CONFIG_MTD_XIP is not set # # Mapping drivers for chip access # # CONFIG_MTD_COMPLEX_MAPPINGS is not set +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_START=0x0 +CONFIG_MTD_PHYSMAP_LEN=0x100000 +CONFIG_MTD_PHYSMAP_BANKWIDTH=2 +# CONFIG_MTD_ARM_INTEGRATOR is not set +# CONFIG_MTD_IMPA7 is not set # CONFIG_MTD_SHARP_SL is not set # CONFIG_MTD_PLATRAM is not set @@ -457,21 +489,17 @@ CONFIG_MTD_NAND_PLATFORM=y # # UBI - Unsorted block images # -# CONFIG_MTD_UBI is not set +CONFIG_MTD_UBI=m +CONFIG_MTD_UBI_WL_THRESHOLD=4096 +CONFIG_MTD_UBI_BEB_RESERVE=1 +CONFIG_MTD_UBI_GLUEBI=y # -# Parallel port support +# UBI debugging options # +# CONFIG_MTD_UBI_DEBUG is not set # CONFIG_PARPORT is not set - -# -# Plug and Play support -# -# CONFIG_PNPACPI is not set - -# -# Block devices -# +CONFIG_BLK_DEV=y # CONFIG_BLK_DEV_COW_COMMON is not set CONFIG_BLK_DEV_LOOP=y # CONFIG_BLK_DEV_CRYPTOLOOP is not set @@ -490,6 +518,7 @@ CONFIG_BLK_DEV_RAM_BLOCKSIZE=1024 # # CONFIG_RAID_ATTRS is not set CONFIG_SCSI=y +CONFIG_SCSI_DMA=y # CONFIG_SCSI_TGT is not set # CONFIG_SCSI_NETLINK is not set # CONFIG_SCSI_PROC_FS is not set @@ -519,36 +548,23 @@ CONFIG_SCSI_WAIT_SCAN=m # CONFIG_SCSI_SPI_ATTRS is not set # CONFIG_SCSI_FC_ATTRS is not set # CONFIG_SCSI_ISCSI_ATTRS is not set -# CONFIG_SCSI_SAS_ATTRS is not set # CONFIG_SCSI_SAS_LIBSAS is not set - -# -# SCSI low-level drivers -# +CONFIG_SCSI_LOWLEVEL=y # CONFIG_ISCSI_TCP is not set # CONFIG_SCSI_DEBUG is not set # CONFIG_ATA is not set - -# -# Multi-device support (RAID and LVM) -# # CONFIG_MD is not set - -# -# Network device support -# CONFIG_NETDEVICES=y +# CONFIG_NETDEVICES_MULTIQUEUE is not set # CONFIG_DUMMY is not set # CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set # CONFIG_EQUALIZER is not set # CONFIG_TUN is not set # CONFIG_PHYLIB is not set - -# -# Ethernet (10 or 100Mbit) -# CONFIG_NET_ETHERNET=y CONFIG_MII=y +# CONFIG_AX88796 is not set # CONFIG_SMC91X is not set CONFIG_DM9000=y # CONFIG_SMC911X is not set @@ -571,16 +587,22 @@ CONFIG_DM9000=y # CONFIG_USB_USBNET_MII is not set # CONFIG_USB_USBNET is not set # CONFIG_WAN is not set -# CONFIG_PPP is not set +CONFIG_PPP=m +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_FILTER is not set +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_MPPE=m +# CONFIG_PPPOE is not set +CONFIG_PPPOL2TP=m # CONFIG_SLIP is not set +CONFIG_SLHC=m # CONFIG_SHAPER is not set # CONFIG_NETCONSOLE is not set # CONFIG_NETPOLL is not set # CONFIG_NET_POLL_CONTROLLER is not set - -# -# ISDN subsystem -# # CONFIG_ISDN is not set # @@ -612,16 +634,21 @@ CONFIG_INPUT_KEYBOARD=y # CONFIG_KEYBOARD_XTKBD is not set # CONFIG_KEYBOARD_NEWTON is not set # CONFIG_KEYBOARD_STOWAWAY is not set -CONFIG_KEYBOARD_PXA27x=m +CONFIG_KEYBOARD_PXA27x=y # CONFIG_KEYBOARD_GPIO is not set # CONFIG_INPUT_MOUSE is not set # CONFIG_INPUT_JOYSTICK is not set # CONFIG_INPUT_TABLET is not set CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_FUJITSU is not set # CONFIG_TOUCHSCREEN_GUNZE is not set # CONFIG_TOUCHSCREEN_ELO is not set # CONFIG_TOUCHSCREEN_MTOUCH is not set # CONFIG_TOUCHSCREEN_MK712 is not set +CONFIG_TOUCHSCREEN_WM97XX=y +# CONFIG_TOUCHSCREEN_WM9705 is not set +CONFIG_TOUCHSCREEN_WM9712=y +# CONFIG_TOUCHSCREEN_WM9713 is not set # CONFIG_TOUCHSCREEN_PENMOUNT is not set # CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set # CONFIG_TOUCHSCREEN_TOUCHWIN is not set @@ -660,58 +687,91 @@ CONFIG_SERIAL_PXA_CONSOLE=y CONFIG_SERIAL_CORE=y CONFIG_SERIAL_CORE_CONSOLE=y CONFIG_UNIX98_PTYS=y -CONFIG_LEGACY_PTYS=y -CONFIG_LEGACY_PTY_COUNT=256 - -# -# IPMI -# +# CONFIG_LEGACY_PTYS is not set # CONFIG_IPMI_HANDLER is not set # CONFIG_WATCHDOG is not set CONFIG_HW_RANDOM=m # CONFIG_NVRAM is not set # CONFIG_R3964 is not set # CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +# CONFIG_I2C_CHARDEV is not set # -# TPM devices +# I2C Algorithms # -# CONFIG_TCG_TPM is not set -# CONFIG_I2C is not set +# CONFIG_I2C_ALGOBIT is not set +# CONFIG_I2C_ALGOPCF is not set +# CONFIG_I2C_ALGOPCA is not set # -# SPI support +# I2C Hardware Bus support # -# CONFIG_SPI is not set -# CONFIG_SPI_MASTER is not set +# CONFIG_I2C_GPIO is not set +CONFIG_I2C_PXA=y +# CONFIG_I2C_PXA_SLAVE is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_SIMTEC is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_STUB is not set +# CONFIG_I2C_TINY_USB is not set # -# Dallas's 1-wire bus +# Miscellaneous I2C Chip support # -# CONFIG_W1 is not set -# CONFIG_HWMON is not set +# CONFIG_SENSORS_DS1337 is not set +# CONFIG_SENSORS_DS1374 is not set +# CONFIG_DS1682 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +CONFIG_DA9030=y +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set # -# Misc devices +# SPI support # +# CONFIG_SPI is not set +# CONFIG_SPI_MASTER is not set +# CONFIG_W1 is not set +CONFIG_POWER_SUPPLY=y +# CONFIG_POWER_SUPPLY_DEBUG is not set +# CONFIG_PDA_POWER is not set +CONFIG_APM_POWER=y +# CONFIG_BATTERY_DS2760 is not set +# CONFIG_BATTERY_EM_X270 is not set +# CONFIG_HWMON is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set # # Multifunction device drivers # # CONFIG_MFD_SM501 is not set - -# -# LED devices -# -# CONFIG_NEW_LEDS is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=m # # LED drivers # +CONFIG_LEDS_GPIO=m +CONFIG_LEDS_EM_X270=m # # LED Triggers # +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=m +CONFIG_LEDS_TRIGGER_HEARTBEAT=m # # Multimedia devices @@ -723,13 +783,17 @@ CONFIG_HW_RANDOM=m # # Graphics support # -# CONFIG_BACKLIGHT_LCD_SUPPORT is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_BACKLIGHT_CORGI=y # # Display device support # # CONFIG_DISPLAY_SUPPORT is not set # CONFIG_VGASTATE is not set +CONFIG_VIDEO_OUTPUT_CONTROL=m CONFIG_FB=y # CONFIG_FIRMWARE_EDID is not set # CONFIG_FB_DDC is not set @@ -752,7 +816,7 @@ CONFIG_FB_DEFERRED_IO=y # # CONFIG_FB_S1D13XXX is not set CONFIG_FB_PXA=y -# CONFIG_FB_PXA_PARAMETERS is not set +CONFIG_FB_PXA_PARAMETERS=y # CONFIG_FB_MBX is not set # CONFIG_FB_VIRTUAL is not set @@ -762,6 +826,7 @@ CONFIG_FB_PXA=y # CONFIG_VGA_CONSOLE is not set CONFIG_DUMMY_CONSOLE=y CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set # CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set # CONFIG_FONTS is not set CONFIG_FONT_8x8=y @@ -774,18 +839,18 @@ CONFIG_LOGO_LINUX_CLUT224=y # # Sound # -CONFIG_SOUND=m +CONFIG_SOUND=y # # Advanced Linux Sound Architecture # -CONFIG_SND=m -CONFIG_SND_TIMER=m -CONFIG_SND_PCM=m +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y # CONFIG_SND_SEQUENCER is not set CONFIG_SND_OSSEMUL=y -CONFIG_SND_MIXER_OSS=m -CONFIG_SND_PCM_OSS=m +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y CONFIG_SND_PCM_OSS_PLUGINS=y # CONFIG_SND_DYNAMIC_MINORS is not set CONFIG_SND_SUPPORT_OLD_API=y @@ -817,17 +882,23 @@ CONFIG_SND_PXA2XX_AC97=m # # System on Chip audio support # -# CONFIG_SND_SOC is not set +CONFIG_SND_SOC_AC97_BUS=y +CONFIG_SND_SOC=y +CONFIG_SND_PXA2XX_SOC=y +CONFIG_SND_PXA2XX_SOC_AC97=y +CONFIG_SND_PXA2XX_SOC_EM_X270=y # -# Open Sound System +# SoC Audio support for SuperH # -# CONFIG_SOUND_PRIME is not set -CONFIG_AC97_BUS=m +CONFIG_SND_SOC_WM9712=y # -# HID Devices +# Open Sound System # +# CONFIG_SOUND_PRIME is not set +CONFIG_AC97_BUS=y +CONFIG_HID_SUPPORT=y CONFIG_HID=y # CONFIG_HID_DEBUG is not set @@ -838,10 +909,7 @@ CONFIG_USB_HID=y # CONFIG_USB_HIDINPUT_POWERBOOK is not set # CONFIG_HID_FF is not set # CONFIG_USB_HIDDEV is not set - -# -# USB support -# +CONFIG_USB_SUPPORT=y CONFIG_USB_ARCH_HAS_HCD=y CONFIG_USB_ARCH_HAS_OHCI=y # CONFIG_USB_ARCH_HAS_EHCI is not set @@ -855,6 +923,7 @@ CONFIG_USB_DEVICEFS=y # CONFIG_USB_DEVICE_CLASS is not set # CONFIG_USB_DYNAMIC_MINORS is not set # CONFIG_USB_SUSPEND is not set +# CONFIG_USB_PERSIST is not set # CONFIG_USB_OTG is not set # @@ -866,12 +935,13 @@ CONFIG_USB_OHCI_HCD=y # CONFIG_USB_OHCI_BIG_ENDIAN_MMIO is not set CONFIG_USB_OHCI_LITTLE_ENDIAN=y # CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set # # USB Device Class drivers # -# CONFIG_USB_ACM is not set -# CONFIG_USB_PRINTER is not set +CONFIG_USB_ACM=m +CONFIG_USB_PRINTER=m # # NOTE: USB_STORAGE enables SCSI, and 'SCSI disk support' @@ -896,8 +966,8 @@ CONFIG_USB_STORAGE=y # # USB Imaging devices # -# CONFIG_USB_MDC800 is not set -# CONFIG_USB_MICROTEK is not set +CONFIG_USB_MDC800=m +CONFIG_USB_MICROTEK=m # CONFIG_USB_MON is not set # @@ -940,25 +1010,25 @@ CONFIG_USB_STORAGE=y # USB Gadget Support # # CONFIG_USB_GADGET is not set -CONFIG_MMC=m +CONFIG_MMC=y # CONFIG_MMC_DEBUG is not set -# CONFIG_MMC_UNSAFE_RESUME is not set +CONFIG_MMC_UNSAFE_RESUME=y # # MMC/SD Card Drivers # -CONFIG_MMC_BLOCK=m +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y # # MMC/SD Host Controller Drivers # -CONFIG_MMC_PXA=m - -# -# Real Time Clock -# +CONFIG_MMC_PXA=y CONFIG_RTC_LIB=y -CONFIG_RTC_CLASS=m +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc1" +# CONFIG_RTC_DEBUG is not set # # RTC interfaces @@ -972,6 +1042,15 @@ CONFIG_RTC_INTF_DEV=y # # I2C RTC drivers # +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set # # SPI RTC drivers @@ -982,14 +1061,29 @@ CONFIG_RTC_INTF_DEV=y # # CONFIG_RTC_DRV_CMOS is not set # CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set # CONFIG_RTC_DRV_DS1742 is not set # CONFIG_RTC_DRV_M48T86 is not set -CONFIG_RTC_DRV_V3020=m +# CONFIG_RTC_DRV_M48T59 is not set +CONFIG_RTC_DRV_V3020=y # # on-CPU RTC drivers # -CONFIG_RTC_DRV_SA1100=m +CONFIG_RTC_DRV_SA1100=y + +# +# DMA Engine support +# +# CONFIG_DMA_ENGINE is not set + +# +# DMA Clients +# + +# +# DMA Devices +# # # File systems @@ -1098,7 +1192,6 @@ CONFIG_SMB_FS=y # CONFIG_NCP_FS is not set # CONFIG_CODA_FS is not set # CONFIG_AFS_FS is not set -# CONFIG_9P_FS is not set # # Partition Types @@ -1167,20 +1260,22 @@ CONFIG_NLS_UTF8=y CONFIG_ENABLE_MUST_CHECK=y CONFIG_MAGIC_SYSRQ=y # CONFIG_UNUSED_SYMBOLS is not set -# CONFIG_DEBUG_FS is not set +CONFIG_DEBUG_FS=y # CONFIG_HEADERS_CHECK is not set CONFIG_DEBUG_KERNEL=y # CONFIG_DEBUG_SHIRQ is not set # CONFIG_DETECT_SOFTLOCKUP is not set +CONFIG_SCHED_DEBUG=y # CONFIG_SCHEDSTATS is not set # CONFIG_TIMER_STATS is not set -# CONFIG_DEBUG_SLAB is not set +# CONFIG_SLUB_DEBUG_ON is not set # CONFIG_DEBUG_RT_MUTEXES is not set # CONFIG_RT_MUTEX_TESTER is not set # CONFIG_DEBUG_SPINLOCK is not set # CONFIG_DEBUG_MUTEXES is not set # CONFIG_DEBUG_LOCK_ALLOC is not set # CONFIG_PROVE_LOCKING is not set +# CONFIG_LOCK_STAT is not set # CONFIG_DEBUG_SPINLOCK_SLEEP is not set # CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set # CONFIG_DEBUG_KOBJECT is not set @@ -1202,10 +1297,6 @@ CONFIG_DEBUG_LL=y # # CONFIG_KEYS is not set # CONFIG_SECURITY is not set - -# -# Cryptographic options -# CONFIG_CRYPTO=y CONFIG_CRYPTO_ALGAPI=m CONFIG_CRYPTO_BLKCIPHER=m @@ -1215,7 +1306,7 @@ CONFIG_CRYPTO_MANAGER=m # CONFIG_CRYPTO_NULL is not set # CONFIG_CRYPTO_MD4 is not set # CONFIG_CRYPTO_MD5 is not set -# CONFIG_CRYPTO_SHA1 is not set +CONFIG_CRYPTO_SHA1=m # CONFIG_CRYPTO_SHA256 is not set # CONFIG_CRYPTO_SHA512 is not set # CONFIG_CRYPTO_WP512 is not set @@ -1243,19 +1334,17 @@ CONFIG_CRYPTO_ARC4=m # CONFIG_CRYPTO_CRC32C is not set # CONFIG_CRYPTO_CAMELLIA is not set # CONFIG_CRYPTO_TEST is not set - -# -# Hardware crypto devices -# +CONFIG_CRYPTO_HW=y # # Library routines # CONFIG_BITREVERSE=y -# CONFIG_CRC_CCITT is not set +CONFIG_CRC_CCITT=m # CONFIG_CRC16 is not set # CONFIG_CRC_ITU_T is not set CONFIG_CRC32=y +# CONFIG_CRC7 is not set # CONFIG_LIBCRC32C is not set CONFIG_ZLIB_INFLATE=y CONFIG_ZLIB_DEFLATE=y diff --git a/arch/arm/mach-pxa/Kconfig b/arch/arm/mach-pxa/Kconfig index 5ebec6d..2957cb9 100644 --- a/arch/arm/mach-pxa/Kconfig +++ b/arch/arm/mach-pxa/Kconfig @@ -148,4 +148,12 @@ config PXA_SSP tristate help Enable support for PXA2xx SSP ports + +config PXA_PWR_I2C + bool "Simple Power I2C interface" + depends on PXA27x + help + Enable support for PXA27x Power I2C interface. This driver + enables very simple blocking access to Power I2C, which + might be useful for early access to Power Management ICs. endif diff --git a/arch/arm/mach-pxa/Makefile b/arch/arm/mach-pxa/Makefile index 7d6ab5c..2d7a431 100644 --- a/arch/arm/mach-pxa/Makefile +++ b/arch/arm/mach-pxa/Makefile @@ -18,7 +18,7 @@ obj-$(CONFIG_PXA_SHARP_Cxx00) += spitz.o corgi_ssp.o corgi_lcd.o sharpsl_pm.o sp obj-$(CONFIG_MACH_AKITA) += akita-ioexp.o obj-$(CONFIG_MACH_POODLE) += poodle.o corgi_ssp.o obj-$(CONFIG_MACH_TOSA) += tosa.o -obj-$(CONFIG_MACH_EM_X270) += em-x270.o +obj-$(CONFIG_MACH_EM_X270) += em-x270.o em-x270-pm.o em-x270-lcd.o em-x270-devices.o # Support for blinky lights led-y := leds.o @@ -29,9 +29,16 @@ led-$(CONFIG_MACH_TRIZEPS4) += leds-trizeps4.o obj-$(CONFIG_LEDS) += $(led-y) +# CPU Frequency scaling +obj-$(CONFIG_CPU_FREQ_PXA) += cpu-pxa.o + # Misc features obj-$(CONFIG_PM) += pm.o sleep.o obj-$(CONFIG_PXA_SSP) += ssp.o +obj-$(CONFIG_PXA_PWR_I2C) += pwr-i2c.o + +#obj-m += da9030_asm.o +#da9030_asm-y += da9030.o da9030_c.o ifeq ($(CONFIG_PXA27x),y) obj-$(CONFIG_PM) += standby.o diff --git a/arch/arm/mach-pxa/cpu-pxa.c b/arch/arm/mach-pxa/cpu-pxa.c new file mode 100644 index 0000000..7fa9703 --- /dev/null +++ b/arch/arm/mach-pxa/cpu-pxa.c @@ -0,0 +1,442 @@ +/* + * linux/arch/arm/mach-pxa/cpu-pxa.c + * + * Copyright (C) 2002,2003 Intrinsyc Software + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * History: + * 31-Jul-2002 : Initial version [FB] + * 29-Jan-2003 : added PXA255 support [FB] + * 20-Apr-2003 : ported to v2.5 (Dustin McIntire, Sensoria Corp.) + * 11-Jan-2006 : v2.6, support for PXA27x processor up to 624MHz (Bill Reese, Hewlett Packard) + * + * Note: + * This driver may change the memory bus clock rate, but will not do any + * platform specific access timing changes... for example if you have flash + * memory connected to CS0, you will need to register a platform specific + * notifier which will adjust the memory access strobes to maintain a + * minimum strobe width. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/cpufreq.h> + +#include <asm/hardware.h> + +#include <asm/arch/pxa-regs.h> + +/* + * This comes from generic.h in this directory. + */ +extern unsigned int get_clk_frequency_khz(int info); + +#define DEBUG 0 + +#ifdef DEBUG + static unsigned int freq_debug = DEBUG; + module_param(freq_debug, int, 0644); + MODULE_PARM_DESC(freq_debug, "Set the debug messages to on=1/off=0"); +#else + #define freq_debug 0 +#endif + +typedef struct +{ + unsigned int khz; /* CPU frequency */ + unsigned int membus; /* memory bus frequency */ + unsigned int cccr; /* new CCLKCFG setting */ + unsigned int div2; /* alter memory controller settings to divide by 2 */ + unsigned int cclkcfg; /* new CCLKCFG setting */ +} pxa_freqs_t; + +/* Define the refresh period in mSec for the SDRAM and the number of rows */ +#define SDRAM_TREF 64 /* standard 64ms SDRAM */ +#if defined(CONFIG_MACH_H4700) || defined(CONFIG_ARCH_H2200) || defined(CONFIG_MACH_MAGICIAN) +#define SDRAM_ROWS 8192 /* hx4700 uses 2 64Mb DRAMs, 8912 rows */ +#else +#define SDRAM_ROWS 4096 /* 64MB=8192 32MB=4096 */ +#endif +#define MDREFR_DRI(x) (((x*SDRAM_TREF/SDRAM_ROWS - 31)/32)) + +#define CCLKCFG_TURBO 0x1 +#define CCLKCFG_FCS 0x2 +#define CCLKCFG_HALFTURBO 0x4 +#define CCLKCFG_FASTBUS 0x8 +#ifdef CONFIG_ARCH_H5400 +/* H5400's SAMCOP chip does not like when K2DB2 touched */ +#define MDREFR_DB2_MASK (MDREFR_K1DB2) +#else +#define MDREFR_DB2_MASK (MDREFR_K2DB2 | MDREFR_K1DB2) +#endif +#define MDREFR_DRI_MASK 0xFFF +#define PXA25x_CCLKCFG CCLKCFG_TURBO | CCLKCFG_FCS + +/* + * For the PXA27x: + * Control variables are A, L, 2N for CCCR; B, HT, T for CLKCFG. + * + * A = 0 => memory controller clock from table 3-7, + * A = 1 => memory controller clock = system bus clock + * Run mode frequency = 13 MHz * L + * Turbo mode frequency = 13 MHz * L * N + * System bus frequency = 13 MHz * L / (B + 1) + * System initialized by bootldr to: + * + * In CCCR: + * A = 1 + * L = 16 oscillator to run mode ratio + * 2N = 6 2 * (turbo mode to run mode ratio) + * + * In CCLKCFG: + * B = 1 Fast bus mode + * HT = 0 Half-Turbo mode + * T = 1 Turbo mode + * + * For now, just support some of the combinations in table 3-7 of + * PXA27x Processor Family Developer's Manual to simplify frequency + * change sequences. + * + * Specify 2N in the PXA27x_CCCR macro, not N! + */ +#define PXA27x_CCCR(A, L, N2) (A << 25 | N2 << 7 | L) +#define PXA27x_CCLKCFG(B, HT, T) (B << 3 | HT << 2 | CCLKCFG_FCS | T) + +#define PXA25x_CCCR(L, M, N) (L << 0 | M << 5 | N << 7) + +/* + * Valid frequency assignments + */ +static pxa_freqs_t pxa2xx_freqs[] = +{ + /* CPU MEMBUS CCCR DIV2 */ +#if defined(CONFIG_PXA25x) +#if defined(CONFIG_PXA25x_ALTERNATE_FREQS) + { 99500, 99500, PXA25x_CCCR(1, 1, 2), 1, PXA25x_CCLKCFG}, /* run=99, turbo= 99, PXbus=50, SDRAM=50 */ + {199100, 99500, PXA25x_CCCR(1, 1, 4), 0, PXA25x_CCLKCFG}, /* run=99, turbo=199, PXbus=50, SDRAM=99 */ + {298500, 99500, PXA25x_CCCR(1, 1, 6), 0, PXA25x_CCLKCFG}, /* run=99, turbo=287, PXbus=50, SDRAM=99 */ + {298600, 99500, PXA25x_CCCR(1, 2, 3), 0, PXA25x_CCLKCFG}, /* run=199, turbo=287, PXbus=99, SDRAM=99 */ + {398100, 99500, PXA25x_CCCR(1, 2, 4), 0, PXA25x_CCLKCFG} /* run=199, turbo=398, PXbus=99, SDRAM=99 */ +#else + { 99500, 99500, PXA25x_CCCR(1, 1, 2), 1, PXA25x_CCLKCFG}, /* run= 99, turbo= 99, PXbus=50, SDRAM=50 */ + {132700, 132700, PXA25x_CCCR(3, 1, 2), 1, PXA25x_CCLKCFG}, /* run=133, turbo=133, PXbus=66, SDRAM=66 */ + {199100, 99500, PXA25x_CCCR(1, 2, 2), 0, PXA25x_CCLKCFG}, /* run=199, turbo=199, PXbus=99, SDRAM=99 */ + {265400, 132700, PXA25x_CCCR(3, 2, 2), 1, PXA25x_CCLKCFG}, /* run=265, turbo=265, PXbus=133, SDRAM=66 */ + {331800, 165900, PXA25x_CCCR(5, 2, 2), 1, PXA25x_CCLKCFG}, /* run=331, turbo=331, PXbus=166, SDRAM=83 */ + {398100, 99500, PXA25x_CCCR(1, 3, 2), 0, PXA25x_CCLKCFG} /* run=398, turbo=398, PXbus=196, SDRAM=99 */ +#endif +#elif defined(CONFIG_PXA27x) + {104000, 104000, PXA27x_CCCR(1, 8, 2), 0, PXA27x_CCLKCFG(1, 0, 1)}, + {156000, 104000, PXA27x_CCCR(1, 8, 6), 0, PXA27x_CCLKCFG(1, 1, 1)}, + {208000, 208000, PXA27x_CCCR(0, 16, 2), 1, PXA27x_CCLKCFG(0, 0, 1)}, + {312000, 208000, PXA27x_CCCR(1, 16, 3), 1, PXA27x_CCLKCFG(1, 0, 1)}, + {416000, 208000, PXA27x_CCCR(1, 16, 4), 1, PXA27x_CCLKCFG(1, 0, 1)}, + {520000, 208000, PXA27x_CCCR(1, 16, 5), 1, PXA27x_CCLKCFG(1, 0, 1)}, +/* {624000, 208000, PXA27x_CCCR(1, 16, 6), 1, PXA27x_CCLKCFG(1, 0, 1)} */ +#endif +}; +#define NUM_FREQS (sizeof(pxa2xx_freqs)/sizeof(pxa_freqs_t)) + +static struct cpufreq_frequency_table pxa2xx_freq_table[NUM_FREQS+1]; + +/* Return the memory clock rate for a given cpu frequency. */ +int pxa_cpufreq_memclk(int cpu_khz) +{ + int i; + int freq_mem = 0; + + for (i = 0; i < NUM_FREQS; i++) { + if (pxa2xx_freqs[i].khz == cpu_khz) { + freq_mem = pxa2xx_freqs[i].membus; + break; + } + } + + return freq_mem; +} +EXPORT_SYMBOL(pxa_cpufreq_memclk); + + +/* find a valid frequency point */ +static int pxa_verify_policy(struct cpufreq_policy *policy) +{ + int ret; + + ret=cpufreq_frequency_table_verify(policy, pxa2xx_freq_table); + + if(freq_debug) { + printk("Verified CPU policy: %dKhz min to %dKhz max\n", + policy->min, policy->max); + } + + return ret; +} + +static int pxa_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + int idx; + cpumask_t cpus_allowed, allowedcpuset; + int cpu = policy->cpu; + struct cpufreq_freqs freqs; + unsigned long flags; + unsigned int unused; + unsigned int preset_mdrefr, postset_mdrefr, cclkcfg; + + if(freq_debug) { + printk ("CPU PXA: target freq %d\n", target_freq); + printk ("CPU PXA: relation %d\n", relation); + } + + /* + * Save this threads cpus_allowed mask. + */ + cpus_allowed = current->cpus_allowed; + + /* + * Bind to the specified CPU. When this call returns, + * we should be running on the right CPU. + */ + cpus_clear (allowedcpuset); + cpu_set (cpu, allowedcpuset); + set_cpus_allowed(current, allowedcpuset); + BUG_ON(cpu != smp_processor_id()); + + /* Lookup the next frequency */ + if (cpufreq_frequency_table_target(policy, pxa2xx_freq_table, + target_freq, relation, &idx)) { + return -EINVAL; + } + + freqs.old = policy->cur; + freqs.new = pxa2xx_freqs[idx].khz; + freqs.cpu = policy->cpu; + if(freq_debug) { + printk(KERN_INFO "Changing CPU frequency to %d Mhz, (SDRAM %d Mhz)\n", + freqs.new/1000, (pxa2xx_freqs[idx].div2) ? + (pxa2xx_freqs[idx].membus/2000) : + (pxa2xx_freqs[idx].membus/1000)); + } + + /* + * Tell everyone what we're about to do... + * you should add a notify client with any platform specific + * Vcc changing capability + */ + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + /* Calculate the next MDREFR. If we're slowing down the SDRAM clock + * we need to preset the smaller DRI before the change. If we're speeding + * up we need to set the larger DRI value after the change. + */ + preset_mdrefr = postset_mdrefr = MDREFR; + if((MDREFR & MDREFR_DRI_MASK) > MDREFR_DRI(pxa2xx_freqs[idx].membus)) { + preset_mdrefr = (preset_mdrefr & ~MDREFR_DRI_MASK) | + MDREFR_DRI(pxa2xx_freqs[idx].membus); + } + postset_mdrefr = (postset_mdrefr & ~MDREFR_DRI_MASK) | + MDREFR_DRI(pxa2xx_freqs[idx].membus); + + /* If we're dividing the memory clock by two for the SDRAM clock, this + * must be set prior to the change. Clearing the divide must be done + * after the change. + */ + if(pxa2xx_freqs[idx].div2) { + /* + * Potentially speeding up memory clock, so slow down the memory + * before speeding up the clock. + */ + preset_mdrefr |= MDREFR_DB2_MASK | MDREFR_K0DB4; + preset_mdrefr &= ~MDREFR_K0DB2; + + postset_mdrefr |= MDREFR_DB2_MASK | MDREFR_K0DB4; + postset_mdrefr &= ~MDREFR_K0DB2; + } else { + /* + * Potentially slowing down memory clock. Wait until after the change + * to speed up the memory. + */ + postset_mdrefr &= ~MDREFR_DB2_MASK; + postset_mdrefr &= ~MDREFR_K0DB4; + postset_mdrefr |= MDREFR_K0DB2; + } + + cclkcfg = pxa2xx_freqs[idx].cclkcfg; + + if (freq_debug) { + printk (KERN_INFO "CPU PXA writing 0x%08x to CCCR\n", + pxa2xx_freqs[idx].cccr); + printk (KERN_INFO "CPU PXA writing 0x%08x to CCLKCFG\n", + pxa2xx_freqs[idx].cclkcfg); + printk (KERN_INFO "CPU PXA writing 0x%08x to MDREFR before change\n", + preset_mdrefr); + printk (KERN_INFO "CPU PXA writing 0x%08x to MDREFR after change\n", + postset_mdrefr); + } + + local_irq_save(flags); + + /* Set new the CCCR */ + CCCR = pxa2xx_freqs[idx].cccr; + + /* + * Should really set both of PMCR[xIDAE] while changing the core frequency + */ + + /* + * TODO: On the PXA27x: If we're setting half-turbo mode and changing the + * core frequency at the same time we must split it up into two operations. + * The current values in the pxa2xx_freqs table don't do this, so the code + * is unimplemented. + */ + + __asm__ __volatile__(" \ + ldr r4, [%1] ; /* load MDREFR */ \ + b 2f ; \ + .align 5 ; \ +1: \ + str %3, [%1] ; /* preset the MDREFR */ \ + mcr p14, 0, %2, c6, c0, 0 ; /* set CCLKCFG[FCS] */ \ + str %4, [%1] ; /* postset the MDREFR */ \ + \ + b 3f ; \ +2: b 1b ; \ +3: nop ; \ + " + : "=&r" (unused) + : "r" (&MDREFR), "r" (cclkcfg), \ + "r" (preset_mdrefr), "r" (postset_mdrefr) + : "r4", "r5"); + local_irq_restore(flags); + + if (freq_debug) { + printk (KERN_INFO "CPU PXA Frequency change successful\n"); + printk (KERN_INFO "CPU PXA new CCSR 0x%08x\n", CCSR); + } + + /* + * Restore the CPUs allowed mask. + */ + set_cpus_allowed(current, cpus_allowed); + + /* + * Tell everyone what we've just done... + * you should add a notify client with any platform specific + * SDRAM refresh timer adjustments + */ + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static int pxa_cpufreq_init(struct cpufreq_policy *policy) +{ + cpumask_t cpus_allowed, allowedcpuset; + unsigned int cpu = policy->cpu; + int i; + unsigned int cclkcfg; + + cpus_allowed = current->cpus_allowed; + + cpus_clear (allowedcpuset); + cpu_set (cpu, allowedcpuset); + set_cpus_allowed(current, allowedcpuset); + BUG_ON(cpu != smp_processor_id()); + + /* set default governor and cpuinfo */ + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; + policy->cpuinfo.transition_latency = 1000; /* FIXME: 1 ms, assumed */ + policy->cur = get_clk_frequency_khz(0); /* current freq */ + + /* Generate the cpufreq_frequency_table struct */ + for(i=0;i<NUM_FREQS;i++) { + pxa2xx_freq_table[i].frequency = pxa2xx_freqs[i].khz; + pxa2xx_freq_table[i].index = i; + } + pxa2xx_freq_table[i].frequency = CPUFREQ_TABLE_END; + + /* + * Set the policy's minimum and maximum frequencies from the tables + * just constructed. This sets cpuinfo.mxx_freq, min and max. + */ + cpufreq_frequency_table_cpuinfo (policy, pxa2xx_freq_table); + + set_cpus_allowed(current, cpus_allowed); + printk(KERN_INFO "PXA CPU frequency change support initialized\n"); + + if (freq_debug) { + printk (KERN_INFO "PXA CPU initial CCCR 0x%08x\n", CCCR); + asm + ( + "mrc p14, 0, %0, c6, c0, 0 ; /* read CCLKCFG from CP14 */ " + : "=r" (cclkcfg) : + ); + printk ("PXA CPU initial CCLKCFG 0x%08x\n", cclkcfg); + printk ("PXA CPU initial MDREFR 0x%08x\n", MDREFR); + } + + return 0; +} + +static unsigned int pxa_cpufreq_get(unsigned int cpu) +{ + cpumask_t cpumask_saved; + unsigned int cur_freq; + + cpumask_saved = current->cpus_allowed; + set_cpus_allowed(current, cpumask_of_cpu(cpu)); + BUG_ON(cpu != smp_processor_id()); + + cur_freq = get_clk_frequency_khz(0); + + set_cpus_allowed(current, cpumask_saved); + + return cur_freq; +} + +static struct cpufreq_driver pxa_cpufreq_driver = { + .verify = pxa_verify_policy, + .target = pxa_set_target, + .init = pxa_cpufreq_init, + .get = pxa_cpufreq_get, +#if defined(CONFIG_PXA25x) + .name = "PXA25x", +#elif defined(CONFIG_PXA27x) + .name = "PXA27x", +#endif +}; + +static int __init pxa_cpu_init(void) +{ + return cpufreq_register_driver(&pxa_cpufreq_driver); +} + +static void __exit pxa_cpu_exit(void) +{ + cpufreq_unregister_driver(&pxa_cpufreq_driver); +} + + +MODULE_AUTHOR ("Intrinsyc Software Inc."); +MODULE_DESCRIPTION ("CPU frequency changing driver for the PXA architecture"); +MODULE_LICENSE("GPL"); +module_init(pxa_cpu_init); +module_exit(pxa_cpu_exit); + diff --git a/arch/arm/mach-pxa/em-x270-devices.c b/arch/arm/mach-pxa/em-x270-devices.c new file mode 100644 index 0000000..d142930 --- /dev/null +++ b/arch/arm/mach-pxa/em-x270-devices.c @@ -0,0 +1,331 @@ +/* + * Support for CompuLab EM-X270 platfrom specific device + * initialization, and per-device power management functions. + * + * Copyright (C) 2007 CompuLab, Ltd. + * Author: Mike Rapoport <mike@compulab.co.il> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/da9030.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#include "../../../drivers/i2c/chips/da9030.h" + +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> + +struct em_x270_dev_data { + int (*is_on)(struct device *dev); + int (*set_on)(struct device *dev, int on); + int (*get_opts)(struct device *dev, + struct device_attribute *attr, + char *buf); + int (*set_opts)(struct device *dev, int opts); +}; + +static void em_x270_dev_release(struct device * dev) +{ + +} + +static ssize_t em_x270_dev_pm_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct em_x270_dev_data *pm_data = dev->platform_data; + int is_on = pm_data->is_on(dev); + + return sprintf(buf, "%d\n", is_on); +} + +static ssize_t em_x270_dev_pm_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char *endp; + int on = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + struct em_x270_dev_data *pm_data = dev->platform_data; + + pr_info("%s: %s\n", __FUNCTION__, buf); + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + pm_data->set_on(dev, on); + + return count; +} + +static DEVICE_ATTR(pwr_on, S_IWUSR | S_IRUGO, + em_x270_dev_pm_show, em_x270_dev_pm_store); + +static int gprs_is_on(struct device *dev) +{ + return !!(pxa_gpio_get_value(20)); +} + +static int gprs_set_on(struct device *dev, int on) +{ + pr_info("%s: on = %d\n", __FUNCTION__, on); + if (on) { + pxa_gpio_mode(20 | GPIO_OUT | GPIO_DFLT_HIGH); + mdelay(500); + pxa_gpio_set_value(82, 0); + mdelay(500); + pxa_gpio_mode(82 | GPIO_OUT | GPIO_DFLT_HIGH); + mdelay(1000); + pxa_gpio_set_value(82, 0); + } + else { + pxa_gpio_set_value(20, 0); + pxa_gpio_mode(20 | GPIO_OUT); + } + + return 0; +} + +static struct em_x270_dev_data em_x270_gprs_data = { + .is_on = gprs_is_on, + .set_on = gprs_set_on, +}; + +static struct platform_device em_x270_gprs = { + .name = "gprs", + .id = -1, + .dev = { + .platform_data = &em_x270_gprs_data, + .release = em_x270_dev_release, + }, +}; + +static int is_wlan_on; + +static int wlan_is_on(struct device *dev) +{ + return is_wlan_on; +} + +static int wlan_set_on(struct device *dev, int on) +{ + if (on) { + /* WLAN power-up sequence */ + /* Mask LDO17 and LDO19 tolerance events */ + da9030_set_reg(IRQ_MASK_C, + IRQ_MASK_C_LDO19 | IRQ_MASK_C_LDO17); + + /* Force I2C control over LDO17, LDO19 */ + da9030_set_reg(SLEEP_CONTROL, + APP_SLEEP_CTL_BYPASS_LDO19 | + APP_SLEEP_CTL_BYPASS_LDO17); + + /* Turn off LDO17 and LDO19 */ + da9030_set_reg(REG_CONTROL_1_17, + RC1_LDO16_EN | RC1_LDO15_EN | + RC1_LDO11_EN | RC1_LDO10_EN | RC1_BUCK2_EN); + da9030_set_reg(REG_CONTROL_2_18, RC2_SIMCP_EN | RC2_LDO18_EN); + + /* Set LDO17 voltage to 3V (VCC_WLAN_IO) */ + da9030_set_reg(LDO_17_SIMCP0, LDO_17_SIMCP0_LDO17_3V); + mdelay(200); + + /* Turn WLAN RF power on */ + pxa_gpio_mode(115 | GPIO_OUT | GPIO_DFLT_HIGH); + + /* Turn LDO17 on */ + da9030_set_reg(REG_CONTROL_1_17, + RC1_LDO17_EN| RC1_LDO16_EN | RC1_LDO15_EN | + RC1_LDO11_EN | RC1_LDO10_EN | RC1_BUCK2_EN); + mdelay(200); + + /* Turn on LDO19 */ + da9030_set_reg(REG_CONTROL_2_18, + RC2_SIMCP_EN | RC2_LDO18_EN | RC2_LDO19_EN); + + } + else { + /* FIXME: implement BGW shutdown */ + } + is_wlan_on = on; + + return 0; +} + +static struct em_x270_dev_data em_x270_wlan_data = { + .is_on = wlan_is_on, + .set_on = wlan_set_on, +}; + +static struct platform_device em_x270_wlan = { + .name = "wlan", + .id = -1, + .dev = { + .platform_data = &em_x270_wlan_data, + .release = em_x270_dev_release, + }, +}; + +static int gps_is_on(struct device *dev) +{ + int val = da9030_get_reg(REG_CONTROL_1_97); + return !!(val & RC3_LDO3_EN); +} + +static int gps_set_on(struct device *dev, int on) +{ + int val = da9030_get_reg(REG_CONTROL_1_97); + + if (on) + val |= RC3_LDO3_EN; + else + val &= ~RC3_LDO3_EN; + da9030_set_reg(REG_CONTROL_1_97, val); + + return 0; +} + +static struct em_x270_dev_data em_x270_gps_data = { + .is_on = gps_is_on, + .set_on = gps_set_on, +}; + +static struct platform_device em_x270_gps = { + .name = "gps", + .id = -1, + .dev = { + .platform_data = &em_x270_gps_data, + .release = em_x270_dev_release, + }, +}; + +static int usb_mode; +static int usb_host_is_on(struct device *dev) +{ + return usb_mode; +} + +static int usb_host_set_on(struct device *dev, int on) +{ + if (on) { + da9030_set_reg(MISC_CONTROLB, MISCB_SESSION_VALID_ENABLE); + da9030_set_reg(USBPUMP, + USB_PUMP_EN_USBVEP | + USB_PUMP_EN_USBVE | + USB_PUMP_USBVE); + + /* enable port 2 transiever */ + UP2OCR = UP2OCR_HXS | UP2OCR_HXOE; + usb_mode = 1; + } + else { + UP2OCR = UP2OCR_DPPUE | UP2OCR_DPPUBE | UP2OCR_HXOE; + da9030_set_reg(USBPUMP, + USB_PUMP_EN_USBVEP | USB_PUMP_EN_USBVE); + usb_mode = 0; + } + + return 0; +} + +static struct em_x270_dev_data em_x270_usb_host_data = { + .is_on = usb_host_is_on, + .set_on = usb_host_set_on, +}; + +static struct platform_device em_x270_usb_host = { + .name = "usb_host", + .id = -1, + .dev = { + .platform_data = &em_x270_usb_host_data, + .release = em_x270_dev_release, + }, +}; + +static struct platform_device *em_x270_devices[] = { + &em_x270_gprs, + &em_x270_wlan, + &em_x270_gps, + &em_x270_usb_host, +}; + +static struct work_struct usb_work; +static void usb_worker(struct work_struct *work) +{ + usb_host_set_on(NULL, !pxa_gpio_get_value(21)); +} + +static irqreturn_t usb_irq_handler(int irq, void *regs) +{ + schedule_work(&usb_work); + + pr_info("%s\n", __FUNCTION__); + return IRQ_HANDLED; +} + +static int em_x270_devices_init(void) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(em_x270_devices); i++ ) { + ret = platform_device_register(em_x270_devices[i]); + if (ret) { + dev_dbg(&em_x270_devices[i]->dev, + "Registration failed: %d\n", ret); + continue; + } + + ret = device_create_file(&em_x270_devices[i]->dev, + &dev_attr_pwr_on); + if (ret) { + dev_dbg(&em_x270_devices[i]->dev, + "PWR_ON attribute failed: %d\n", ret); + } + + dev_dbg(&em_x270_devices[i]->dev, + "Registered PWR ON attribute\n"); + } + + /* setup USB detection irq */ + INIT_WORK(&usb_work, usb_worker); + set_irq_type(IRQ_GPIO(21), IRQT_BOTHEDGE); + ret = request_irq(IRQ_GPIO(21), usb_irq_handler, IRQF_DISABLED, + "usb detect", 0); + if (ret) { + pr_info("USB device detection disabled\n"); + } + else { + schedule_work(&usb_work); + } + + return 0; +} + +static void em_x270_devices_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(em_x270_devices); i++ ) { + device_remove_file(&em_x270_devices[i]->dev, + &dev_attr_pwr_on); + platform_device_unregister(em_x270_devices[i]); + } +} + +late_initcall(em_x270_devices_init); +module_exit(em_x270_devices_exit); + +MODULE_AUTHOR("Mike Rapoport"); +MODULE_DESCRIPTION("Support for platfrom specific device attributes for EM-X270"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-pxa/em-x270-lcd.c b/arch/arm/mach-pxa/em-x270-lcd.c new file mode 100644 index 0000000..437efe1 --- /dev/null +++ b/arch/arm/mach-pxa/em-x270-lcd.c @@ -0,0 +1,223 @@ +/* + * LCD initialization and backlight managemnet for EM-X270 + * + * Copyright (C) 2007 CompuLab, Ltd. + * Author: Igor Vaisbein <igor@compulab.co.il> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/da9030.h> +#include <linux/delay.h> + +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/sharpsl.h> +#include <asm/arch/ssp.h> + +#include "devices.h" + +#define GPIO87_nPCE_2 87 /* Card Enable for Card Space (PXA27x) */ +#define GPIO87_nPCE_2_MD (87 | GPIO_ALT_FN_1_IN) +#define GPIO87_USB3_1_MD (87 | GPIO_ALT_FN_3_IN) +#define GPIO87_SSPTXD2_MD (87 | GPIO_ALT_FN_1_OUT) +#define GPIO87_SSFRM2_MD (87 | GPIO_ALT_FN_3_OUT) + +#define LCCR4 __REG(0x44000010) + +#define TD035STEE1_LCD_ID 0x2008 + +static void em_x270_set_bl_intensity(int intensity) +{ + da9030_set_wled((intensity != 0), intensity); +} + +struct corgibl_machinfo em_x270_bl_machinfo = { + .max_intensity = 0x7, + .default_intensity = 0x3, + .limit_mask = 0x1, + .set_bl_intensity = em_x270_set_bl_intensity, +}; + +static void em_x270_bl_release(struct device * dev) +{ + +} + +static struct platform_device em_x270_bl = { + .name = "corgi-bl", + .dev = { + .release = em_x270_bl_release, + .parent = &pxa_device_fb.dev, + .platform_data = &em_x270_bl_machinfo, + }, + .id = -1, +}; + +/* + * Helper functions to access LCD throuhg SPI interface + */ +static void set_ssp_9bit(void) +{ + int temp; + SSCR0 = 0xFF208; + SSCR0 = 0xFF288; + SSCR1 = 0x40000018; + + while (SSSR & 0x8) + temp = SSDR; +} + +static void set_ssp_18bit(void) +{ + int temp; + SSCR0 = 0x1FF201; + SSCR0 = 0x1FF281; + SSCR1 = 0x40000018; + while (SSSR & 0x8) + temp = SSDR; +} + +static void set_ssp_rcv(void) +{ + int temp; + SSCR0 = 0x1FF20F; + SSCR0 = 0x1FF28F; + SSCR1 = 0x40000018; + while (SSSR & 0x8) + temp = SSDR; +} + +static void send_ssp_9bit(unsigned int value) +{ + int temp; + SSDR = (value); + + asm volatile ("mcr p15, 0, r0, c7, c10, 4":::"r0"); + + if (!(SSSR & 0x4)) + while ((SSSR & 0xf00) == 0) + ; + + while ((SSSR & 0xf00) != 0) + ; + while (SSSR & 0x10) + ; + while (SSSR & 0x8) + temp = SSDR; +} + +static void send_ssp_18bit(unsigned int value) +{ + int temp; + SSDR = (value); + + asm volatile ("mcr p15, 0, r0, c7, c10, 4":::"r0"); + + if (!(SSSR & 0x4)) + while ((SSSR & 0xf00) == 0) + ; + + while ((SSSR & 0xf00) != 0) + ; + while (SSSR & 0x10) + ; + while (SSSR & 0x8) + temp = SSDR; +} + +static unsigned int rcv_ssp_18bit(void) +{ + SSDR = ((0x04) << 23); + asm volatile ("mcr p15, 0, r0, c7, c10, 4":::"r0"); + if (!(SSSR & 0x4)) + while ((SSSR & 0xf00) == 0) + ; + while ((SSSR & 0xf00) != 0) + ; + while (SSSR & 0x10) + ; + return SSDR; +} + +/* LCD init sequence */ +int em_x270_lcd_detect(void) +{ + unsigned int data; + + /* Reset the LCD module */ + pxa_gpio_mode(GPIO87_nPCE_2 | GPIO_OUT); + GPCR(GPIO87_nPCE_2) |= GPIO_bit(GPIO87_nPCE_2); + mdelay(75); + GPSR(GPIO87_nPCE_2) |= GPIO_bit(GPIO87_nPCE_2); + mdelay(70); + + /* TD035STEE1 LCD_SSP initialization commands */ + set_ssp_9bit(); + send_ssp_9bit(0x000); + mdelay(5); + + send_ssp_9bit(0x000); + mdelay(5); + + send_ssp_9bit(0x000); + mdelay(5); + + set_ssp_18bit(); + send_ssp_18bit(0x17980); + mdelay(5); + + send_ssp_18bit(0x17F10); + mdelay(5); + + set_ssp_9bit(); + send_ssp_9bit(0x011); + mdelay(50); + + send_ssp_9bit(0x029); + mdelay(10); + + set_ssp_rcv(); + + /* Check for LCD ID, to enable the back-light */ + data = rcv_ssp_18bit(); + + if ((data & 0xFFFF) != TD035STEE1_LCD_ID) + return -ENODEV; + + /* enable backlight */ + da9030_set_wled(1, 2); + return 0; +} + +static int em_x270_lcd_init(void) +{ + int ret; + pr_debug("%s\n", __FUNCTION__); + ret = em_x270_lcd_detect(); + if (ret) + return ret; + + /* make sure we keep LCCR4 with PCD=0 */ + LCCR4 = 0x0; + + return platform_device_register(&em_x270_bl); +} + +static void em_x270_lcd_exit(void) +{ + pr_debug("%s\n", __FUNCTION__); + platform_device_unregister(&em_x270_bl); +} + +late_initcall(em_x270_lcd_init); +module_exit(em_x270_lcd_exit); + +MODULE_DESCRIPTION("EM-X270 backlight and LCD initialization driver"); +MODULE_AUTHOR("Mike Rapoport"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-pxa/em-x270-pm.c b/arch/arm/mach-pxa/em-x270-pm.c new file mode 100644 index 0000000..55ba4cd --- /dev/null +++ b/arch/arm/mach-pxa/em-x270-pm.c @@ -0,0 +1,892 @@ +/* + * Support for CompuLab EM-X270 platform power management + * + * Copyright (C) 2007 CompuLab, Ltd. + * Author: Mike Rapoport <mike@compulab.co.il> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/sysfs.h> +#include <linux/pm.h> +#include <linux/da9030.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/ctype.h> + +#include <linux/apm-emulation.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include "../../../drivers/i2c/chips/da9030.h" + +#include <asm/arch/pm.h> +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/pwr-i2c.h> + +#define DA9030_ADDR 0x92 + +#define EM_X270_BATCHK_TIME_SUSPEND (10*60) /* 10 min */ + +#define VOLTAGE_MAX_DESIGN 4200000 /* 4.2V in uV */ +#define VOLTAGE_MIN_DESIGN 3000000 /* 3V in uV */ + +#define REG2VOLT(x) ((((x) * 2650) >> 8) + 2650) +#define VOLT2REG(x) ((((x) - 2650) << 8) / 2650) + +#define REG2CURR(x) ((((x) * 24000) >> 8) / 15) + +#define VCHARGE_MIN_THRESHOLD VOLT2REG(3200) +#define VCHARGE_MAX_THRESHOLD VOLT2REG(5500) + +#define VBAT_LOW_THRESHOLD VOLT2REG(3600) +#define VBAT_CRIT_THRESHOLD VOLT2REG(3400) + +#define VBAT_CHARGE_START VOLT2REG(4100) +#define VBAT_CHARGE_STOP VOLT2REG(4200) +#define VBAT_CHARGE_RESTART VOLT2REG(4000) + +#define TBAT_LOW_THRESHOLD 197 /* 0oC */ +#define TBAT_HIGH_THRESHOLD 78 /* 45oC */ +#define TBAT_RESUME_THRESHOLD 100 /* 35oC */ + +struct em_x270_charger; + +struct em_x270_charger_ops { + void (*get_status)(struct em_x270_charger *charger); + void (*set_charge)(struct em_x270_charger *charger, int on); + + s32 (*da9030_get_reg)(u32 reg); + s32 (*da9030_set_reg)(u32 reg, u8 val); +}; + +struct em_x270_charger { + struct device *dev; + + struct power_supply bat; + struct da9030_adc_res adc; + struct delayed_work work; + + int interval; + + int da9030_status; + int da9030_fault; + int mA; + int mV; + int is_on; + + struct em_x270_charger_ops *ops; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debug_file; +#endif +}; + +static struct em_x270_charger *the_charger; + +static unsigned short tbat_readings[] = { + 300, 244, 200, 178, 163, 152, 144, 137, 131, + 126, 122, 118, 114, 111, 108, 105, 103, 101, + 98, 96, 94, 93, 91, 89, 88, 86, 85, + 83, 82, 81, 79, 78, 77, 76, 75, 74, + 73, 72, 71, 70, 69, 68, 67, 67, 66, + 65, 64, 63, 63, 62, 61, 60, 60, 59, + 58, 58, 57, 57, 56, 55, 55, 54, 53, + 53, 52, 52, 51, 51, 50, 50, 49, 49, + 48, 48, 47, 47, 46, 46, 45, 45, 44, + 44, 43, 43, 42, 42, 41, 41, 41, 40, + 40, 39, 39, 38, 38, 38, 37, 37, 36, + 36, 35, 35, 35, 34, 34, 34, 33, 33, + 32, 32, 32, 31, 31, 30, 30, 30, 29, + 29, 29, 28, 28, 28, 27, 27, 26, 26, + 26, 25, 25, 25, 24, 24, 24, 23, 23, + 23, 22, 22, 21, 21, 21, 20, 20, 20, + 19, 19, 19, 18, 18, 18, 17, 17, 17, + 16, 16, 16, 15, 15, 14, 14, 14, 13, + 13, 13, 12, 12, 12, 11, 11, 11, 10, + 10, 10, 9, 9, 8, 8, 8, 7, 7, + 7, 6, 6, 5, 5, 5, 4, 4, 3, + 3, 3, 2, 2, 1, 1, 1, 0, 0, + -1, -1, -1, -2, -2, -3, -3, -4, -4, + -5, -5, -6, -6, -6, -7, -7, -8, -9, + -9, -10, -10, -11, -11, -12, -12, -13, -14, + -14, -15, -16, -16, -17, -18, -18, -19, -20, + -21, -21, -22, -23, -24, -25, -26, -27, -28, + -30, -31, -32, -34, -35, -37, -39, -41, -44, + -47, -50, -56, -64, +}; + +static inline int usb_host_on(void) +{ + return !pxa_gpio_get_value(21); +} + +#ifdef CONFIG_DEBUG_FS + +static int debug_show(struct seq_file *s, void *data) +{ + struct em_x270_charger *charger = s->private; + + seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off"); + if (charger->da9030_status & CHRG_CHARGER_ENABLE) { + seq_printf(s, "iset = %dmA, vset = %dmV\n", + charger->mA, charger->mV); + } + + seq_printf(s, "vbat_res = %d (%dmV)\n", + charger->adc.vbat_res, REG2VOLT(charger->adc.vbat_res)); + seq_printf(s, "vbatmin_res = %d (%dmV)\n", + charger->adc.vbatmin_res, + REG2VOLT(charger->adc.vbatmin_res)); + seq_printf(s, "vbatmintxon = %d (%dmV)\n", + charger->adc.vbatmintxon, + REG2VOLT(charger->adc.vbatmintxon)); + seq_printf(s, "ichmax_res = %d (%dmA)\n", + charger->adc.ichmax_res, + REG2CURR(charger->adc.ichmax_res)); + seq_printf(s, "ichmin_res = %d (%dmA)\n", + charger->adc.ichmin_res, + REG2CURR(charger->adc.ichmin_res)); + seq_printf(s, "ichaverage_res = %d (%dmA)\n", + charger->adc.ichaverage_res, + REG2CURR(charger->adc.ichaverage_res)); + seq_printf(s, "vchmax_res = %d (%dmV)\n", + charger->adc.vchmax_res, + REG2VOLT(charger->adc.vchmax_res)); + seq_printf(s, "vchmin_res = %d (%dmV)\n", + charger->adc.vchmin_res, + REG2VOLT(charger->adc.vchmin_res)); + seq_printf(s, "tbat_res = %d (%doC)\n", charger->adc.tbat_res, + tbat_readings[charger->adc.tbat_res]); + seq_printf(s, "adc_in4_res = %d\n", charger->adc.adc_in4_res); + seq_printf(s, "adc_in5_res = %d\n", charger->adc.adc_in5_res); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, debug_show, inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry* em_x270_create_debugfs(struct em_x270_charger *charger) +{ + charger->debug_file = debugfs_create_file("charger", 0666, 0, charger, + &debug_fops); + return charger->debug_file; +} + +static void em_x270_remove_debugfs(struct em_x270_charger *charger) +{ + debugfs_remove(charger->debug_file); +} +#else +#define em_x270_create_debugfs(x) NULL +#define em_x270_remove_debugfs(x) do {} while(0) +#endif + +/*********************************************************************/ +/* DA9030 access functions for suspend/resume */ +#define DA_ADDR 0x49 +static inline s32 __da9030_get_reg(u32 reg) +{ + pr_info("%s: reg = %d\n", __FUNCTION__, reg); + return pxa_pwr_i2c_reg_read(DA_ADDR, reg); +} + +static inline s32 __da9030_set_reg(u32 reg, u8 val) +{ + pr_info("%s: reg = %d, val = %d\n", __FUNCTION__, reg, val); + return pxa_pwr_i2c_reg_write(DA_ADDR, reg, val); +} + +/*********************************************************************/ +/* helpers for charger state monnitor. Different version for stready + * state and suspended system + */ +static void em_x270_get_charger_status(struct em_x270_charger *charger) +{ + da9030_get_charger(&charger->is_on, &charger->mA, &charger->mV); + da9030_read_adc(&charger->adc); + charger->da9030_status = da9030_get_status(); + charger->da9030_fault = da9030_get_fault_log(); +} + +static void em_x270_set_charge(struct em_x270_charger *charger, int on) +{ + if (on) { + pr_debug("%s: enabling charger\n", __FUNCTION__); + da9030_set_thresholds(TBAT_HIGH_THRESHOLD, + TBAT_RESUME_THRESHOLD, + TBAT_LOW_THRESHOLD, + VBAT_LOW_THRESHOLD); + da9030_set_reg(CCTR_CONTROL, CCTR_SET_8MIN); + da9030_set_charger(1, 1000, 4200); + da9030_set_led(3, 1, 0, 0, 0); + charger->is_on = 1; + } + else { + /* disable charger */ + pr_debug("%s: disabling charger\n", __FUNCTION__); + da9030_set_charger(0, 0, 0); + da9030_set_led(3, 0, 0, 0, 0); + charger->is_on = 0; + } +} + +static void em_x270_get_charger_status_suspend(struct em_x270_charger *charger) +{ + s32 val; + + val = __da9030_get_reg(CHARGE_CONTROL); + charger->is_on = (val & CHRG_CHARGER_ENABLE) ? 1 : 0; + charger->mA = ((val >> 3) & 0xf) * 100; + charger->mV = (val & 0x7) * 50 + 4000; + + charger->adc.vbat_res = __da9030_get_reg(VBAT_RES); + charger->adc.vchmax_res = __da9030_get_reg(VCHMAX_RES); + charger->adc.vchmin_res = __da9030_get_reg(VCHMIN_RES); + charger->adc.tbat_res = __da9030_get_reg(TBAT_RES); + + charger->da9030_status = __da9030_get_reg(STATUS); + charger->da9030_fault = __da9030_get_reg(FAULT_LOG); +} + +static void em_x270_set_charge_suspend(struct em_x270_charger *charger, int on) +{ + if (on) { + u8 val = 0; + int mA = 1000; + int mV = 4200; + + pr_debug("%s: enabling charger\n", __FUNCTION__); + val = CHRG_CHARGER_ENABLE; + val |= (mA / 100) << 3; + val |= (mV - 4000) / 50; + + __da9030_set_reg(CCTR_CONTROL, CCTR_SET_8MIN); + __da9030_set_reg(VBATMON, VBAT_LOW_THRESHOLD); + __da9030_set_reg(CHARGE_CONTROL, val); + charger->is_on = 1; + } + else { + /* disable charger */ + pr_debug("%s: disabling charger\n", __FUNCTION__); + __da9030_set_reg(CHARGE_CONTROL, 0); + charger->is_on = 0; + } +} + +static struct em_x270_charger_ops em_x270_charger_ops = { + .get_status = em_x270_get_charger_status, + .set_charge = em_x270_set_charge, + .da9030_get_reg = da9030_get_reg, + .da9030_set_reg = da9030_set_reg, +}; + +static struct em_x270_charger_ops em_x270_charger_ops_suspend = { + .get_status = em_x270_get_charger_status_suspend, + .set_charge = em_x270_set_charge_suspend, + .da9030_get_reg = __da9030_get_reg, + .da9030_set_reg = __da9030_set_reg, +}; + +/*********************************************************************/ +/* charging state machine */ +static void em_x270_check_charger_state(struct em_x270_charger *charger) +{ + charger->ops->get_status(charger); + +/* we wake or boot with external power on */ + if (!charger->is_on) { + if ((charger->da9030_status & STATUS_CHDET) && + (!usb_host_on()) && + (charger->adc.vbat_res < VBAT_CHARGE_START)) { + pr_debug("%s: vbat_res <= 4100\n", __FUNCTION__); + charger->ops->set_charge(charger, 1); + } + } + else { + if (charger->adc.vbat_res >= VBAT_CHARGE_STOP) { + pr_debug("%s: vbat_res >= 4200\n", __FUNCTION__); + charger->ops->set_charge(charger, 0); + charger->ops->da9030_set_reg(VBATMON, + VBAT_CHARGE_RESTART); + } + else if (charger->adc.vbat_res > VBAT_LOW_THRESHOLD) { + /* we are charging and passed LOW_THRESH, + so upate DA9030 VBAT threshold + */ + pr_debug("%s: vbat_res >= %d\n", __FUNCTION__, + REG2VOLT(VBAT_LOW_THRESHOLD)); + charger->ops->da9030_set_reg(VBATMON, + VBAT_LOW_THRESHOLD); + } + if (charger->adc.vchmax_res > VCHARGE_MAX_THRESHOLD || + charger->adc.vchmin_res < VCHARGE_MIN_THRESHOLD || + /* Tempreture readings are negative */ + charger->adc.tbat_res < TBAT_HIGH_THRESHOLD || + charger->adc.tbat_res > TBAT_LOW_THRESHOLD ) { + /* disable charger */ + pr_info("%s: thresholds fail\n", __FUNCTION__); + charger->ops->set_charge(charger, 0); + } + } +} + +static void em_x270_charging_monitor(struct work_struct *work) +{ + struct em_x270_charger *charger; + + charger = container_of(work, struct em_x270_charger, work.work); + + em_x270_check_charger_state(charger); + + /* reschedule for the next time */ + schedule_delayed_work(&charger->work, charger->interval); +} + +void em_x270_battery_release(struct device * dev) +{ +} + +struct em_x270_battery_thresh { + int voltage; + int percentage; +}; + +static struct em_x270_battery_thresh vbat_ranges[] = { + { 150, 100}, + { 149, 99}, + { 148, 98}, + { 147, 98}, + { 146, 97}, + { 145, 96}, + { 144, 96}, + { 143, 95}, + { 142, 94}, + { 141, 93}, + { 140, 92}, + { 139, 91}, + { 138, 90}, + { 137, 90}, + { 136, 89}, + { 135, 88}, + { 134, 88}, + { 133, 87}, + { 132, 86}, + { 131, 85}, + { 130, 83}, + { 129, 82}, + { 128, 81}, + { 127, 81}, + { 126, 80}, + { 125, 75}, + { 124, 74}, + { 123, 73}, + { 122, 70}, + { 121, 66}, + { 120, 65}, + { 119, 64}, + { 118, 64}, + { 117, 63}, + { 116, 59}, + { 115, 58}, + { 114, 57}, + { 113, 57}, + { 112, 56}, + { 111, 50}, + { 110, 49}, + { 109, 49}, + { 108, 48}, + { 107, 48}, + { 106, 33}, + { 105, 32}, + { 104, 32}, + { 103, 32}, + { 102, 31}, + { 101, 16}, + { 100, 15}, + { 99, 15}, + { 98, 15}, + { 97, 10}, + { 96, 9}, + { 95, 7}, + { 94, 3}, + { 93, 0}, +}; + +static enum power_supply_property em_x270_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, /* in percents! */ + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static void em_x270_bat_check_status(struct em_x270_charger *charger, + union power_supply_propval *val) +{ + /* FIXME: below code is very crude approximation of actual + states, we need to take into account voltage and current + measurements to determine actual charger state */ + if (charger->da9030_status & STATUS_CHDET) { + if (!usb_host_on()) { + if (charger->is_on) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } + else { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } + } + else { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } +} + +static void em_x270_bat_check_health(struct em_x270_charger *charger, + union power_supply_propval *val) +{ + if (charger->da9030_fault & FAULT_LOG_OVER_TEMP) { + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } + else if (charger->da9030_fault & FAULT_LOG_VBAT_OVER) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } + else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } +} + +static int vbat_interpolate(int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vbat_ranges); i++ ) + if (vbat_ranges[i].voltage == reg) { + pr_debug("%s: voltage = %d, percentage = %d\n", + __FUNCTION__, vbat_ranges[i].voltage, + vbat_ranges[i].percentage); + return vbat_ranges[i].percentage; + } + + return 0; +} + +static int em_x270_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + u32 reg; + struct em_x270_charger *charger; + charger = container_of(psy, struct em_x270_charger, bat); + + switch(psp) { + case POWER_SUPPLY_PROP_STATUS: + em_x270_bat_check_status(charger, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + em_x270_bat_check_health(charger, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = VOLTAGE_MAX_DESIGN; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = VOLTAGE_MIN_DESIGN; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + reg = charger->adc.vbat_res; + /* V = (reg / 256) * 2.65 + 2.65 (V) */ + val->intval = ((reg * 2650000) >> 8) + 2650000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + reg = charger->adc.ichaverage_res; + val->intval = reg; /* reg */ + break; + case POWER_SUPPLY_PROP_CAPACITY: + reg = charger->adc.vbat_res; + val->intval = vbat_interpolate(reg); + break; + case POWER_SUPPLY_PROP_TEMP: + reg = charger->adc.tbat_res; + val->intval = tbat_readings[reg]; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "MaxPower"; + pr_debug("%s: MFG = %s\n", __FUNCTION__, val->strval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "LP555597P6H-FPS"; + pr_debug("%s: MODEL = %s\n", __FUNCTION__, val->strval); + break; + default: break; + } + + return 0; +} + +static void em_x270_setup_battery(struct power_supply *bat) +{ + bat->name = "em-x270-battery"; + bat->type = POWER_SUPPLY_TYPE_BATTERY; + bat->properties = em_x270_bat_props; + bat->num_properties = ARRAY_SIZE(em_x270_bat_props); + bat->get_property = em_x270_bat_get_property; + bat->use_for_apm = 1; +}; + +static void em_x270_chiover_callback(int event, void *_charger) +{ + /* disable charger */ + struct em_x270_charger *charger = _charger; +/* pr_info("%s\n", __FUNCTION__); */ + em_x270_set_charge(charger, 0); +} + +static void em_x270_tbat_callback(int event, void *_charger) +{ + /* disable charger */ + struct em_x270_charger *charger = _charger; +/* pr_info("%s\n", __FUNCTION__); */ + em_x270_set_charge(charger, 0); +} + +static void em_x270_vbat_callback(int event, void *_charger) +{ + struct em_x270_charger *charger = _charger; + da9030_read_adc(&charger->adc); + + if (!charger->is_on) { + if (charger->adc.vbat_res < VBAT_LOW_THRESHOLD) { + /* set VBAT threshold for critical */ + da9030_set_reg(VBATMON, VBAT_CRIT_THRESHOLD); + da9030_set_reg(VBATMON_1, VBAT_CRIT_THRESHOLD); + apm_queue_event(APM_LOW_BATTERY); + } + else if (charger->adc.vbat_res < VBAT_CRIT_THRESHOLD) { + /* notify the system of battery critical */ + apm_queue_event(APM_CRITICAL_SUSPEND); + } + } +} + +static void em_x270_chdet_callback(int event, void *_charger) +{ + struct em_x270_charger *charger = _charger; + int status = da9030_get_status(); + pr_info("%s\n", __FUNCTION__); + +/* em_x270_check_charger_state(charger); */ + + /* check if we have "real" charger or our own USB pump */ + if (!usb_host_on()) + em_x270_set_charge(charger, !!(status & CHRG_CHARGER_ENABLE)); +} + +static int em_x270_battery_probe(struct platform_device *pdev) +{ + struct em_x270_charger *charger; + + pr_debug("%s\n", __FUNCTION__); + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (charger == NULL) { + return -ENOMEM; + } + + the_charger = charger; + + charger->dev = &pdev->dev; + + charger->ops = &em_x270_charger_ops; + + charger->interval = 10 * HZ; /* 10 seconds between monotor runs */ + em_x270_setup_battery(&charger->bat); + + platform_set_drvdata(pdev, charger); + + da9030_enable_adc(); + + INIT_DELAYED_WORK(&charger->work, em_x270_charging_monitor); + schedule_delayed_work(&charger->work, charger->interval); + + charger->debug_file = em_x270_create_debugfs(charger); + + em_x270_setup_battery(&charger->bat); + + da9030_register_callback(DA9030_IRQ_CHDET, + em_x270_chdet_callback, + charger); + da9030_register_callback(DA9030_IRQ_VBATMON, + em_x270_vbat_callback, + charger); + + /* critical condition events */ + da9030_register_callback(DA9030_IRQ_CHIOVER, + em_x270_chiover_callback, + charger); + da9030_register_callback(DA9030_IRQ_TBAT, + em_x270_tbat_callback, + charger); + + da9030_set_thresholds(TBAT_HIGH_THRESHOLD, + TBAT_RESUME_THRESHOLD, + TBAT_LOW_THRESHOLD, + VBAT_LOW_THRESHOLD); + + power_supply_register(&pdev->dev, &charger->bat); + + return 0; +} + +static int em_x270_battery_remove(struct platform_device *dev) +{ + struct em_x270_charger *charger = platform_get_drvdata(dev); + + pr_debug("%s\n", __FUNCTION__); + em_x270_remove_debugfs(charger); + cancel_delayed_work(&charger->work); + power_supply_unregister(&charger->bat); + + kfree(charger); + the_charger = NULL; + + return 0; +} + +static int em_x270_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + pr_info("%s\n", __FUNCTION__); + da9030_set_reg(REG_CONTROL_1_97, RC3_BUCK_EN | RC3_LDO6_EN); + da9030_set_reg(REG_CONTROL_2_98, 0); + da9030_set_reg(REG_CONTROL_2_18,RC2_LDO18_EN); + da9030_set_reg(REG_CONTROL_1_17, + RC1_LDO16_EN | RC1_LDO15_EN | RC1_BUCK2_EN); + da9030_set_reg(WLED_CONTROL, 0); + + da9030_set_reg(LED_1_CONTROL, 0); + da9030_set_reg(LED_4_CONTROL, 0); + + return 0; +} + +extern int em_x270_lcd_detect(void); +static int em_x270_battery_resume(struct platform_device *pdev) +{ + pr_info("%s\n", __FUNCTION__); + + da9030_set_reg(LED_1_CONTROL, 0xff); + da9030_set_reg(LED_4_CONTROL, 0xff); + + da9030_set_reg(REG_CONTROL_1_97, + RC3_BUCK_EN | RC3_LDO1_EN | RC3_LDO2_EN | + RC3_LDO3_EN | RC3_LDO6_EN | RC3_LDO7_EN); + da9030_set_reg(REG_CONTROL_2_98, + RC4_SIMCP_ENABLE | RC4_LDO11_EN | + RC4_LDO9_EN | RC4_LDO8_EN); + da9030_set_reg(REG_CONTROL_2_18, + RC2_SIMCP_EN | RC2_LDO18_EN | RC2_LDO19_EN); + da9030_set_reg(REG_CONTROL_1_17, + RC1_LDO17_EN| RC1_LDO16_EN | RC1_LDO15_EN | + RC1_LDO11_EN | RC1_LDO10_EN | RC1_BUCK2_EN); + + if (em_x270_lcd_detect() != 0) + pr_info("%s: LCD resume failed\n", __FUNCTION__); + + return 0; +} + +static struct platform_driver em_x270_battery_driver = { + .driver = { + .name = "em-x270-battery", + .owner = THIS_MODULE, + }, + .probe = em_x270_battery_probe, + .remove = em_x270_battery_remove, + + .suspend_late = em_x270_battery_suspend, + .resume_early = em_x270_battery_resume, +}; + +/**************************************************************************/ +/* global patform power management */ +/**************************************************************************/ +/* suspend/resume button */ +static irqreturn_t em_x270_suspend_irq(int irq, void *data) +{ + apm_queue_event(APM_USER_SUSPEND); + return IRQ_HANDLED; +} + +static void em_x270_goto_sleep(suspend_state_t state, + unsigned long alarm_time, + unsigned int alarm_enable) +{ + pr_info("%s\n", __FUNCTION__); + + the_charger->ops = &em_x270_charger_ops_suspend; + + RTSR &= RTSR_ALE; + RTAR = RCNR + EM_X270_BATCHK_TIME_SUSPEND; + + pxa_pm_enter(state); +} + +static int em_x270_enter_suspend(unsigned long alarm_time, + unsigned int alarm_enable) +{ + s32 status = 0; + s32 event_a, event_b; + int ret; + int retry = 10; + pr_info("%s\n", __FUNCTION__); + + /* make sure power I2C state is consistent */ + PWRICR &= ~ICR_IUE; + + if ((PEDR & PWER_GPIO1)) { + /* button pressed */ + /* clear pending event to avoid interrupt*/ + PEDR &= ~PWER_GPIO1; + GEDR0 &= ~GPIO_bit(1); + + ret = 0; + } + else if ((PEDR & PWER_RTC)) { + PEDR &= ~PWER_RTC; + pr_debug("%s: timer alarm\n", __FUNCTION__); + em_x270_check_charger_state(the_charger); + ret = 1; + } + else if ((PEDR & PWER_GPIO0)) { + PEDR &= ~PWER_GPIO0; + GEDR0 &= ~GPIO_bit(0); + + /* we woke up because of DA9030 event */ + do { + status = the_charger->ops->da9030_get_reg(STATUS); + udelay(1000); + } while (status < 0 && retry--); + + the_charger->da9030_status = status; + event_a = the_charger->ops->da9030_get_reg(EVENT_A); + event_b = the_charger->ops->da9030_get_reg(EVENT_B); + + pr_info("%s: DA9030 wakeup: status: %x, ev_a: %x, ev_b: %x\n", + __FUNCTION__, the_charger->da9030_status, + event_a, event_b); + +/* if ((the_charger->da9030_status & STATUS_CHDET)) { */ + if (event_a & EVENT_A_CHDET) { + pr_info("%s: EVENT_A_CHDET\n", __FUNCTION__); + em_x270_check_charger_state(the_charger); + } + else { + pr_info("%s: What the hell?\n", __FUNCTION__); + } + + ret = 1; + } + else { + /* wake up is not DA9030, so continue and let battery + driver check the charger state */ + ret = 0; + } + + /* get back to sleep */ + if (ret) + em_x270_goto_sleep(PM_SUSPEND_MEM, alarm_time, alarm_enable); + + return ret; +} + +static int em_x270_pm_enter(suspend_state_t state) +{ + unsigned long alarm_time = RTAR; + unsigned int alarm_status = ((RTSR & RTSR_ALE) != 0); + + /* pre-suspend */ + pr_info("suspending emma\n"); + pxa_gpio_mode(38 | GPIO_OUT | GPIO_DFLT_HIGH); + + em_x270_goto_sleep(state, alarm_time, alarm_status); + + /* check if we were resumed because of charger events */ + while (em_x270_enter_suspend(alarm_time, alarm_status)) + {} + + /* make sure power I2C state is consistent */ + PWRICR &= ~ICR_IUE; + + /* resume */ + pr_info("emma is resumed\n"); + the_charger->ops = &em_x270_charger_ops; + + return 0; +} + +static struct pm_ops em_x270_pm_ops = { + .enter = em_x270_pm_enter, + .valid = pm_valid_only_mem, +}; + +static int em_x270_pm_init(void) +{ + int ret; + pm_set_ops(&em_x270_pm_ops); + + set_irq_type(IRQ_GPIO(1), IRQT_RISING); + + ret = platform_driver_register(&em_x270_battery_driver); + if (ret) + return ret; + + ret = request_irq(IRQ_GPIO(1), em_x270_suspend_irq, IRQF_DISABLED, + "suspend button", 0); + if (ret) { + platform_driver_unregister(&em_x270_battery_driver); + } + + return ret; +} + +static void em_x270_pm_exit(void) +{ + free_irq(IRQ_GPIO(1), em_x270_suspend_irq); + platform_driver_unregister(&em_x270_battery_driver); +} + +/* make sure I2C is already running */ +late_initcall(em_x270_pm_init); +module_exit(em_x270_pm_exit); + +MODULE_DESCRIPTION("EM-X270 power manager"); +MODULE_AUTHOR("Mike Rapoport"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-pxa/em-x270.c b/arch/arm/mach-pxa/em-x270.c index 3d0ad50..402d792 100644 --- a/arch/arm/mach-pxa/em-x270.c +++ b/arch/arm/mach-pxa/em-x270.c @@ -18,20 +18,26 @@ #include <linux/mtd/nand.h> #include <linux/mtd/partitions.h> -#include <asm/mach-types.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <asm/mach-types.h> #include <asm/mach/arch.h> #include <asm/arch/pxa-regs.h> #include <asm/arch/pxafb.h> #include <asm/arch/ohci.h> #include <asm/arch/mmc.h> +#include <asm/arch/pxa27x_keyboard.h> #include <asm/arch/bitfield.h> +#include <asm/arch/udc.h> + +#include <asm/arch/sharpsl.h> + #include "generic.h" /* GPIO IRQ usage */ -#define EM_X270_MMC_PD (105) #define EM_X270_ETHIRQ IRQ_GPIO(41) #define EM_X270_MMC_IRQ IRQ_GPIO(13) @@ -213,6 +219,68 @@ static struct platform_device em_x270_nand = { } }; +/* DA9030 */ +static struct i2c_board_info em_x270_pmic_info = { + .driver_name = "da9030", + .type = "pmic", + .addr = 0x49, + .irq = IRQ_GPIO(0), +}; + +/* Keypad */ +/* The Demo KeyPad has the following mapping: + * (0,0) (1,2) (2,1) + * (0,2) (1,1) (2,0) + * (0,1) (1,0) (2,2) + */ +static struct pxa27x_keyboard_platform_data em_x270_kbd = { + .nr_rows = 3, + .nr_cols = 3, + .keycodes = { + { /* row 0 */ + -1, + -1, + KEY_LEFT, + }, + { /* row 1 */ + KEY_UP, + KEY_ENTER, + KEY_DOWN + }, + { /* row 2 */ + KEY_RIGHT, + -1, + -1 + }, + }, + .gpio_modes = { + (100 | GPIO_ALT_FN_1_IN), + (101 | GPIO_ALT_FN_1_IN), + (102 | GPIO_ALT_FN_1_IN), + (103 | GPIO_ALT_FN_2_OUT), + (104 | GPIO_ALT_FN_2_OUT), + (105 | GPIO_ALT_FN_2_OUT), + }, +}; + +static struct platform_device em_x270_pxa_keypad = { + .name = "pxa27x-keyboard", + .id = -1, + .dev = { + .platform_data = &em_x270_kbd, + }, +}; + +static struct platform_device em_x270_battery_device = { + .name = "em-x270-battery", + .id = -1, +}; + +static struct platform_device em_x270_led_device = { + .name = "em-x270-led", + .id = -1, +}; + /* platform devices */ static struct platform_device *platform_devices[] __initdata = { &em_x270_dm9k, @@ -220,6 +288,9 @@ static struct platform_device *platform_devices[] __initdata = { &em_x270_ts, &em_x270_rtc, &em_x270_nand, + &em_x270_pxa_keypad, + &em_x270_battery_device, + &em_x270_led_device, }; @@ -241,6 +312,33 @@ static struct pxaohci_platform_data em_x270_ohci_platform_data = { .init = em_x270_ohci_init, }; +/* + * USB Client (Gadget/UDC) + */ +static void em_x270_udc_command(int cmd) +{ + switch(cmd) { + case PXA2XX_UDC_CMD_CONNECT: + UP2OCR = UP2OCR_HXOE | UP2OCR_DMPUE | UP2OCR_DMPUBE; + break; + case PXA2XX_UDC_CMD_DISCONNECT: +/* UP2OCR = UP2OCR_HXS | UP2OCR_HXOE; */ + //UP2OCR = UP2OCR_HXOE | UP2OCR_DMPUE | UP2OCR_DMPUBE; + break; + } +} + +static int em_x270_udc_detect(void) +{ + return 1; +} + +static struct pxa2xx_udc_mach_info em_x270_udc_info __initdata = { + .udc_is_connected = em_x270_udc_detect, + .udc_command = em_x270_udc_command, +}; + + static int em_x270_mci_init(struct device *dev, irq_handler_t em_x270_detect_int, @@ -256,9 +354,6 @@ static int em_x270_mci_init(struct device *dev, pxa_gpio_mode(GPIO110_MMCDAT2_MD); pxa_gpio_mode(GPIO111_MMCDAT3_MD); - /* EM-X270 uses GPIO13 as SD power enable */ - pxa_gpio_mode(EM_X270_MMC_PD | GPIO_OUT); - err = request_irq(EM_X270_MMC_IRQ, em_x270_detect_int, IRQF_DISABLED | IRQF_TRIGGER_FALLING, "MMC card detect", data); @@ -313,12 +408,19 @@ static struct pxafb_mach_info em_x270_lcd = { .num_modes = 1, .cmap_inverse = 0, .cmap_static = 0, - .lccr0 = LCCR0_PAS, - .lccr3 = LCCR3_PixClkDiv(0x01) | LCCR3_Acb(0xff), + + .lccr0 = LCCR0_Act, + .lccr3 = LCCR3_PixFlEdg, }; static void __init em_x270_init(void) { + /* register PMIC */ + i2c_register_board_info(1, &em_x270_pmic_info, 1); + + /* setup DA9030 irq */ + set_irq_type(IRQ_GPIO(0), IRQT_FALLING); + /* setup LCD */ set_pxa_fb_info(&em_x270_lcd); @@ -329,6 +431,8 @@ static void __init em_x270_init(void) pxa_set_mci_info(&em_x270_mci_platform_data); pxa_set_ohci_info(&em_x270_ohci_platform_data); + pxa_set_udc_info(&em_x270_udc_info); + /* setup STUART GPIOs */ pxa_gpio_mode(GPIO46_STRXD_MD); pxa_gpio_mode(GPIO47_STTXD_MD); @@ -341,9 +445,16 @@ static void __init em_x270_init(void) /* Setup interrupt for dm9000 */ set_irq_type(EM_X270_ETHIRQ, IRQT_RISING); + + PCFR = 0x6; + PSLR = 0xff400000; + PMCR = 0x00000005; + PWER = 0x80000003; + PFER = 0x00000003; + PRER = 0x00000000; } -MACHINE_START(EM_X270, "Compulab EM-x270") +MACHINE_START(EM_X270, "Compulab EM-X270") .boot_params = 0xa0000100, .phys_io = 0x40000000, .io_pg_offst = (io_p2v(0x40000000) >> 18) & 0xfffc, diff --git a/arch/arm/mach-pxa/pwr-i2c.c b/arch/arm/mach-pxa/pwr-i2c.c new file mode 100644 index 0000000..8a501c4 --- /dev/null +++ b/arch/arm/mach-pxa/pwr-i2c.c @@ -0,0 +1,539 @@ +/* + * (C) Copyrihgt 2007 CompuLab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * Adaptation of U-Boot I2C driver for PXA. + * + * (C) Copyright 2000 + * Paolo Scaffardi, AIRVENT SAM s.p.a - RIMINI(ITALY), arsenio@tin.it + * + * (C) Copyright 2000 Sysgo Real-Time Solutions, GmbH <www.elinos.com> + * Marius Groeger <mgroeger@sysgo.de> + * + * (C) Copyright 2003 Pengutronix e.K. + * Robert Schwebel <r.schwebel@pengutronix.de> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * Back ported to the 8xx platform (from the 8260 platform) by + * Murray.Jensen@cmst.csiro.au, 27-Jan-01. + */ + +/* #define DEBUG */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/jiffies.h> + +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/pwr-i2c.h> + +#define I2C_ICR_INIT (ICR_BEIE | ICR_IRFIE | ICR_ITEIE | ICR_GCD | ICR_SCLE) +#define I2C_ISR_INIT 0x7FF + +/* Shall the current transfer have a start/stop condition? */ +#define I2C_COND_NORMAL 0 +#define I2C_COND_START 1 +#define I2C_COND_STOP 2 + +/* Shall the current transfer be ack/nacked or being waited for it? */ +#define I2C_ACKNAK_WAITACK 1 +#define I2C_ACKNAK_SENDACK 2 +#define I2C_ACKNAK_SENDNAK 4 + +/* Specify who shall transfer the data (master or slave) */ +#define I2C_READ 0 +#define I2C_WRITE 1 + +/* All transfers are described by this data structure */ +struct i2c_msg { + u8 condition; + u8 acknack; + u8 direction; + u8 data; +}; + +/** + * pxa_pwr_i2c_transfer: - reset the host controller + * + */ +/* static void pxa_pwr_i2c_reset(void) */ +/* { */ +/* int i; */ + +/* /\* CKEN |= CKEN_PWRI2C; *\/ */ +/* /\* PWRICR |= PCFR_PI2C_EN; *\/ */ + +/* /\* /\\* delay 250ms *\\/ *\/ */ +/* /\* for (i = 0; i < 250; i++) *\/ */ +/* /\* udelay(1000); *\/ */ + +/* /\* PWRICR &= ~(ICR_MA | ICR_START | ICR_STOP); *\/ */ +/* /\* PWRICR |= ICR_UR; *\/ */ +/* /\* PWRISR = 0x7ff; *\/ */ + +/* /\* PWRICR &= ~ICR_UR; *\/ */ +/* /\* PWRICR = ICR_GCD | ICR_SCLE; *\/ */ +/* /\* PWRICR |= ICR_IUE; *\/ */ +/* /\* PWRICR |= 0x8000; *\/ */ + +/* /\* udelay(1000); *\/ */ + +/* return; */ + +/* PWRICR &= ~ICR_IUE; /\* disable unit *\/ */ +/* PWRICR |= ICR_UR; /\* reset the unit *\/ */ +/* udelay(100); */ +/* PWRICR &= ~ICR_IUE; /\* disable unit *\/ */ + +/* CKEN |= CKEN_PWRI2C; */ +/* PCFR |= PCFR_PI2C_EN; */ + +/* PWRICR = I2C_ICR_INIT; /\* set control register values *\/ */ +/* PWRISR = I2C_ISR_INIT; /\* set clear interrupt bits *\/ */ +/* PWRICR |= ICR_IUE; /\* enable unit *\/ */ +/* udelay(100); */ +/* } */ + +static void pwr_i2c_abort() +{ + unsigned long timeout = 250000; + unsigned long time = 0; + + while ((time < timeout) && (PWRIBMR & 0x1) == 0) { + unsigned long icr = PWRICR; + + icr &= ~ICR_START; + icr |= ICR_ACKNAK | ICR_STOP | ICR_TB; + + PWRICR = icr; + + udelay(1000); + time += 1000; + } + + PWRICR &= ~(ICR_MA | ICR_START | ICR_STOP); +} + +static void pxa_pwr_i2c_reset(void) +{ + pr_debug("Resetting I2C Controller Unit\n"); + + /* abort any transfer currently under way */ + pwr_i2c_abort(); + + /* reset according to 9.8 */ + PWRICR = ICR_UR; + PWRISR = I2C_ISR_INIT; + PWRISR &= ~ICR_UR; + + /* set control register values */ + PWRICR = I2C_ICR_INIT; + + /* enable unit */ + PWRICR |= ICR_IUE; + udelay(100); + +/* CKEN |= CKEN_PWRI2C; */ +/* PCFR |= PCFR_PI2C_EN; */ +} + +/** + * i2c_isr_set_cleared: - wait until certain bits of the I2C status register + * are set and cleared + * + * @return: 1 in case of success, 0 means timeout (no match within 10 ms). + */ +static int pxa_pwr_i2c_isr_set_cleared(unsigned long set_mask, + unsigned long cleared_mask) +{ + int timeout = 10000; + + while (((PWRISR & set_mask) != set_mask) + || ((PWRISR & cleared_mask) != 0)) { + udelay(10); + if (timeout-- < 0) + return 0; + } + + return 1; +} + +/** + * pxa_pwr_i2c_transfer: - Transfer one byte over the i2c bus + * + * This function can tranfer a byte over the i2c bus in both directions. + * It is used by the public API functions. + * + * @return: 0: transfer successful + * -EINVAL: message is empty + * -ETIMEDOUT: transmit timeout + * -E: ACK missing + * -ETIMEDOUT: receive timeout + * -EINVAL: illegal parameters + * -EBUSY: bus is busy and couldn't be aquired + */ +int pxa_pwr_i2c_transfer(struct i2c_msg *msg) +{ + int ret; + + if (!msg) + goto transfer_error_msg_empty; + + switch (msg->direction) { + case I2C_WRITE: + /* check if bus is not busy */ +/* if (!pxa_pwr_i2c_isr_set_cleared(0, (ISR_IBB | ISR_UB))) */ +/* goto transfer_error_bus_busy; */ + + /* start transmission */ + PWRICR &= ~ICR_START; + PWRICR &= ~ICR_STOP; + PWRIDBR = msg->data; + if (msg->condition == I2C_COND_START) + PWRICR |= ICR_START; + if (msg->condition == I2C_COND_STOP) + PWRICR |= ICR_STOP; + if (msg->acknack == I2C_ACKNAK_SENDNAK) + PWRICR |= ICR_ACKNAK; + if (msg->acknack == I2C_ACKNAK_SENDACK) + PWRICR &= ~ICR_ACKNAK; + PWRICR &= ~ICR_ALDIE; + PWRICR |= ICR_TB; + + /* transmit register empty? */ + if (!pxa_pwr_i2c_isr_set_cleared(ISR_ITE, 0)) + goto transfer_error_transmit_timeout; + + /* clear 'transmit empty' state */ + PWRISR |= ISR_ITE; + + /* wait for ACK from slave */ + if (msg->acknack == I2C_ACKNAK_WAITACK) + if (!pxa_pwr_i2c_isr_set_cleared(0, ISR_ACKNAK)) + goto transfer_error_ack_missing; + break; + case I2C_READ: + /* check if bus is not busy */ +/* if (!pxa_pwr_i2c_isr_set_cleared(0, ISR_IBB)) */ +/* goto transfer_error_bus_busy; */ + + /* start receive */ + PWRICR &= ~ICR_START; + PWRICR &= ~ICR_STOP; + if (msg->condition == I2C_COND_START) + PWRICR |= ICR_START; + if (msg->condition == I2C_COND_STOP) + PWRICR |= ICR_STOP; + if (msg->acknack == I2C_ACKNAK_SENDNAK) + PWRICR |= ICR_ACKNAK; + if (msg->acknack == I2C_ACKNAK_SENDACK) + PWRICR &= ~ICR_ACKNAK; + PWRICR &= ~ICR_ALDIE; + PWRICR |= ICR_TB; + + /* receive register full? */ + if (!pxa_pwr_i2c_isr_set_cleared(ISR_IRF, 0)) + goto transfer_error_receive_timeout; + + msg->data = PWRIDBR; + + /* clear 'receive empty' state */ + PWRISR |= ISR_IRF; + + break; + default: + goto transfer_error_illegal_param; + + } + + return 0; + +transfer_error_msg_empty: + pr_debug("%s: error: 'msg' is empty\n", __FUNCTION__); + ret = -1; + goto i2c_transfer_finish; + +transfer_error_transmit_timeout: + pr_debug("%s: error: transmit timeout\n", __FUNCTION__); + ret = -2; + goto i2c_transfer_finish; + +transfer_error_ack_missing: + pr_debug("%s: error: ACK missing\n", __FUNCTION__); + ret = -3; + goto i2c_transfer_finish; + +transfer_error_receive_timeout: + pr_debug("%s: error: receive timeout\n", __FUNCTION__); + ret = -4; + goto i2c_transfer_finish; + +transfer_error_illegal_param: + pr_debug("%s: error: illegal parameters\n", __FUNCTION__); + ret = -5; + goto i2c_transfer_finish; + +transfer_error_bus_busy: + pr_debug("%s: error: bus is busy\n", __FUNCTION__); + ret = -6; + goto i2c_transfer_finish; + +i2c_transfer_finish: + pr_debug("%s: ISR: 0x%04x\n", __FUNCTION__, ISR); + return ret; +} + +/* ------------------------------------------------------------------------ */ +/* API Functions */ +/* ------------------------------------------------------------------------ */ +/** + * i2c_probe: - Test if a chip answers for a given i2c address + * + * @chip: address of the chip which is searched for + * @return: 0 if a chip was found, -1 otherwhise + */ +int pxa_pwr_i2c_probe(u8 chip) +{ + struct i2c_msg msg; + int ret; + + pxa_pwr_i2c_reset(); + + msg.condition = I2C_COND_START; + msg.acknack = I2C_ACKNAK_WAITACK; + msg.direction = I2C_WRITE; + msg.data = (chip << 1) + 1; + if ((ret = pxa_pwr_i2c_transfer(&msg))) + return ret; + + msg.condition = I2C_COND_STOP; + msg.acknack = I2C_ACKNAK_SENDNAK; + msg.direction = I2C_READ; + msg.data = 0x00; + if ((ret = pxa_pwr_i2c_transfer(&msg))) + return ret; + + return 0; +} + +/** + * i2c_read: - Read multiple bytes from an i2c device + * + * The higher level routines take into account that this function is only + * called with len < page length of the device (see configuration file) + * + * @chip: address of the chip which is to be read + * @addr: i2c data address within the chip + * @alen: length of the i2c data address (1..2 bytes) + * @buffer: where to write the data + * @len: how much byte do we want to read + * @return: 0 in case of success + */ +int pxa_pwr_i2c_read(u8 chip, uint addr, int alen, u8 * buffer, int len) +{ + struct i2c_msg msg; + u8 addr_bytes[3]; /* lowest...highest byte of data address */ + int ret; + + pr_debug("%s(chip=0x%02x, addr=0x%02x, alen=0x%02x, len=0x%02x)\n", + __FUNCTION__, chip, addr, alen, len); + + pxa_pwr_i2c_reset(); + + /* dummy chip address write */ + pr_debug("%s: dummy chip address write\n", __FUNCTION__); + msg.condition = I2C_COND_START; + msg.acknack = I2C_ACKNAK_WAITACK; + msg.direction = I2C_WRITE; + msg.data = (chip << 1); + msg.data &= 0xFE; + if ((ret = pxa_pwr_i2c_transfer(&msg))) + return ret; + + /* + * send memory address bytes; + * alen defines how much bytes we have to send. + */ + /*addr &= ((1 << CFG_EEPROM_PAGE_WRITE_BITS)-1); */ + addr_bytes[0] = (u8) ((addr >> 0) & 0x000000FF); + addr_bytes[1] = (u8) ((addr >> 8) & 0x000000FF); + addr_bytes[2] = (u8) ((addr >> 16) & 0x000000FF); + + while (--alen >= 0) { + pr_debug("%s: send memory word address byte %1d\n", + __FUNCTION__, alen); + msg.condition = I2C_COND_NORMAL; + msg.acknack = I2C_ACKNAK_WAITACK; + msg.direction = I2C_WRITE; + msg.data = addr_bytes[alen]; + if ((ret = pxa_pwr_i2c_transfer(&msg))) + return ret; + } + + /* start read sequence */ + pr_debug("%s: start read sequence\n", __FUNCTION__); + msg.condition = I2C_COND_START; + msg.acknack = I2C_ACKNAK_WAITACK; + msg.direction = I2C_WRITE; + msg.data = (chip << 1); + msg.data |= 0x01; + if ((ret = pxa_pwr_i2c_transfer(&msg))) + return ret; + + /* read bytes; send NACK at last byte */ + while (len--) { + if (len == 0) { + msg.condition = I2C_COND_STOP; + msg.acknack = I2C_ACKNAK_SENDNAK; + } else { + msg.condition = I2C_COND_NORMAL; + msg.acknack = I2C_ACKNAK_SENDACK; + } + + msg.direction = I2C_READ; + msg.data = 0x00; + if ((ret = pxa_pwr_i2c_transfer(&msg))) + return ret; + + *buffer = msg.data; + pr_debug("%s: reading byte (0x%08x)=0x%02x\n", + __FUNCTION__, (unsigned int)buffer, *buffer); + buffer++; + } + + pxa_pwr_i2c_reset(); + return 0; +} + +/** + * i2c_write: - Write multiple bytes to an i2c device + * + * The higher level routines take into account that this function is only + * called with len < page length of the device (see configuration file) + * + * @chip: address of the chip which is to be written + * @addr: i2c data address within the chip + * @alen: length of the i2c data address (1..2 bytes) + * @buffer: where to find the data to be written + * @len: how much byte do we want to read + * @return: 0 in case of success + */ +int pxa_pwr_i2c_write(u8 chip, uint addr, int alen, u8 * buffer, int len) +{ + struct i2c_msg msg; + u8 addr_bytes[3]; /* lowest...highest byte of data address */ + int ret; + + pr_debug("%s(chip=0x%02x, addr=0x%02x, alen=0x%02x, len=0x%02x)\n", + __FUNCTION__, chip, addr, alen, len); + + pxa_pwr_i2c_reset(); + + /* chip address write */ + pr_debug("%s: chip address write\n", __FUNCTION__); + msg.condition = I2C_COND_START; + msg.acknack = I2C_ACKNAK_WAITACK; + msg.direction = I2C_WRITE; + msg.data = (chip << 1); + msg.data &= 0xFE; + if ((ret = pxa_pwr_i2c_transfer(&msg))) + return ret; + + /* + * send memory address bytes; + * alen defines how much bytes we have to send. + */ + addr_bytes[0] = (u8) ((addr >> 0) & 0x000000FF); + addr_bytes[1] = (u8) ((addr >> 8) & 0x000000FF); + addr_bytes[2] = (u8) ((addr >> 16) & 0x000000FF); + + while (--alen >= 0) { + pr_debug("%s: send memory word address\n", __FUNCTION__); + msg.condition = I2C_COND_NORMAL; + msg.acknack = I2C_ACKNAK_WAITACK; + msg.direction = I2C_WRITE; + msg.data = addr_bytes[alen]; + if ((ret = pxa_pwr_i2c_transfer(&msg))) + return ret; + } + + /* write bytes; send NACK at last byte */ + while (len--) { + pr_debug("%s: writing byte (0x%08x)=0x%02x\n", + __FUNCTION__, (unsigned int)buffer, *buffer); + + if (len == 0) + msg.condition = I2C_COND_STOP; + else + msg.condition = I2C_COND_NORMAL; + + msg.acknack = I2C_ACKNAK_WAITACK; + msg.direction = I2C_WRITE; + msg.data = *(buffer++); + + if ((ret = pxa_pwr_i2c_transfer(&msg))) + return ret; + } + + pxa_pwr_i2c_reset(); + return 0; +} + +/** + * pxa_pwr_i2c_reg_read: - Read single byte from an i2c device + * + * @chip: address of the chip which is to be read + * @reg: i2c data address within the chip + * @return: data in case of success, negative error code otherwise + */ +s32 pxa_pwr_i2c_reg_read(u8 chip, u8 reg) +{ + char buf; + int ret; + + pr_debug("%s(chip=0x%02x, reg=0x%02x)\n", __FUNCTION__, chip, reg); + ret = pxa_pwr_i2c_read(chip, reg, 1, &buf, 1); + if (ret == 0) + ret = buf; + + return ret; +} + +/** + * pxa_pwr_i2c_reg_write: - Write multiple bytes to an i2c device + * + * The higher level routines take into account that this function is only + * called with len < page length of the device (see configuration file) + * + * @chip: address of the chip which is to be written + * @reg: i2c data address within the chip + * @return: 0 in case of success, negative error code otherwise + */ +s32 pxa_pwr_i2c_reg_write(u8 chip, u8 reg, u8 val) +{ + pr_debug("%s(chip=0x%02x, reg=0x%02x, val=0x%02x)\n", + __FUNCTION__, chip, reg, val); + return pxa_pwr_i2c_write(chip, reg, 1, &val, 1); +} + +MODULE_DESCRIPTION("PXA Power I2C"); +MODULE_AUTHOR("Mike Rapoport"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-pxa/pxa27x.c b/arch/arm/mach-pxa/pxa27x.c index 203371a..36b695f 100644 --- a/arch/arm/mach-pxa/pxa27x.c +++ b/arch/arm/mach-pxa/pxa27x.c @@ -243,7 +243,11 @@ void pxa27x_cpu_pm_enter(suspend_state_t state) case PM_SUSPEND_MEM: /* set resume return address */ PSPR = virt_to_phys(pxa_cpu_resume); - pxa27x_cpu_suspend(PWRMODE_SLEEP); +#ifdef CONFIG_MACH_EM_X270 + pxa27x_cpu_suspend(PWRMODE_DEEPSLEEP); +#else + pxa27x_cpu_suspend(PWRMODE_SLEEP); +#endif break; } } diff --git a/arch/arm/mach-pxa/spitz.c b/arch/arm/mach-pxa/spitz.c index bae47e1..a16e532 100644 --- a/arch/arm/mach-pxa/spitz.c +++ b/arch/arm/mach-pxa/spitz.c @@ -349,6 +349,32 @@ static struct pxamci_platform_data spitz_mci_platform_data = { /* + * USB Client (Gadget/UDC) + */ +static void spitz_udc_command(int cmd) +{ + switch(cmd) { + case PXA2XX_UDC_CMD_CONNECT: + UP2OCR = UP2OCR_HXOE | UP2OCR_DMPUE | UP2OCR_DMPUBE; + break; + case PXA2XX_UDC_CMD_DISCONNECT: + //UP2OCR = UP2OCR_HXOE | UP2OCR_DMPUE | UP2OCR_DMPUBE; + break; + } +} + +static int spitz_udc_detect(void) +{ + return 1; +} + +static struct pxa2xx_udc_mach_info spitz_udc_info __initdata = { + .udc_is_connected = spitz_udc_detect, + .udc_command = spitz_udc_command, +}; + + +/* * USB Host (OHCI) */ static int spitz_ohci_init(struct device *dev) @@ -499,6 +525,7 @@ static void __init common_init(void) pxa_gpio_mode(SPITZ_GPIO_HSYNC | GPIO_IN); platform_add_devices(devices, ARRAY_SIZE(devices)); + pxa_set_udc_info(&spitz_udc_info); pxa_set_mci_info(&spitz_mci_platform_data); pxa_set_ohci_info(&spitz_ohci_platform_data); pxa_set_ficp_info(&spitz_ficp_platform_data); diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index 2e1c24f..f80f2b1 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -163,4 +163,17 @@ config MENELAUS and other features that are often used in portable devices like cell phones and PDAs. +config DA9030 + tristate "Dialog Semiconductor DA9030 power management chip" + depends on EXPERIMENTAL && EMBEDDED + help + If you say yes here you get support for the Dialog + Semiconductor DA9030 power management chip. + This includes voltage regulators, lithium ion/polymer battery + charging, and other features that are often used in portable + devices like PDAs, cell phones and cameras. + + This driver can also be built as a module. If so, the module + will be called da9030. + endmenu diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index ca924e1..69f545c 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o +obj-$(CONFIG_DA9030) += da9030.o ifeq ($(CONFIG_I2C_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/i2c/chips/da9030.c b/drivers/i2c/chips/da9030.c new file mode 100644 index 0000000..9791272 --- /dev/null +++ b/drivers/i2c/chips/da9030.c @@ -0,0 +1,1213 @@ +/* + * Dialog Semiconductor DA9030 power management IC driver + * + * Copyright (C) 2007 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * Some parts based on menelaus.c: + * Copyright (C) 2004 Texas Instruments, Inc. + * Copyright (C) 2005, 2006 Nokia Corporation + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/module.h> +#include <linux/ctype.h> +#include <linux/uaccess.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/da9030.h> +#include <linux/kernel_stat.h> +#include <linux/random.h> +#include <linux/mutex.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include "da9030.h" + +#define DRIVER_NAME "da9030" + +static unsigned short normal_i2c[] = { 0x49, I2C_CLIENT_END }; + +I2C_CLIENT_INSMOD; + +struct da9030 { + struct mutex lock; + struct i2c_client *client; + + struct work_struct event_work; + + /* there are 24 interrupts */ + void (*callbacks[24])(int event, void *data); + void *callbacks_data[24]; + + struct dentry *debug_file; +}; + +/* I hardly believe there can be more than 1 such chip in the system, + so keeping its global instance won't really hurt */ +static struct da9030 *the_da9030; + +unsigned char defined_regs[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, + 0x50, 0x51, + 0x60, 0x61, 0x62, 0x63, + 0x80, 0x81, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, +}; + +static inline int is_reg_valid(u32 reg) +{ + int i; + for (i = 0; i < ARRAY_SIZE(defined_regs); i++) + if (reg == defined_regs[i]) + return 1; + + return 0; +} + +s32 da9030_get_reg(u32 reg) +{ + if (!is_reg_valid(reg)) + return -EINVAL; + + return i2c_smbus_read_byte_data(the_da9030->client, reg); +} +EXPORT_SYMBOL(da9030_get_reg); + +s32 da9030_set_reg(u32 reg, u8 val) +{ + if (!is_reg_valid(reg)) + return -EINVAL; + + return i2c_smbus_write_byte_data(the_da9030->client, reg, val); +} +EXPORT_SYMBOL(da9030_set_reg); + +static s32 da9030_update_reg_bits(u32 reg, int bits, int shift, int val) +{ + int ret; + int new_val; + + mutex_lock(&the_da9030->lock); + ret = da9030_get_reg(reg); + if (ret < 0) + goto out; + + new_val = reg & ~(((1 << bits) - 1) << shift); + new_val |= (val << shift); + + ret = da9030_set_reg(reg, new_val); + mutex_unlock(&the_da9030->lock); + +out: + return ret; +} + +int da9030_get_status(void) +{ + s32 ret; + + mutex_lock(&the_da9030->lock); + ret = da9030_get_reg(STATUS); + mutex_unlock(&the_da9030->lock); + + return ret; +} +EXPORT_SYMBOL(da9030_get_status); + +int da9030_get_fault_log(void) +{ + s32 ret; + + mutex_lock(&the_da9030->lock); + ret = da9030_get_reg(FAULT_LOG); + mutex_unlock(&the_da9030->lock); + + return ret; +} +EXPORT_SYMBOL(da9030_get_fault_log); + +void da9030_read_adc(struct da9030_adc_res *adc) +{ + mutex_lock(&the_da9030->lock); + + adc->vbat_res = da9030_get_reg(VBAT_RES); + adc->vbatmin_res = da9030_get_reg(VBATMIN_RES); + adc->vbatmintxon = da9030_get_reg(VBATMINTXON_RES); + adc->ichmax_res = da9030_get_reg(ICHMAX_RES); + adc->ichmin_res = da9030_get_reg(ICHMIN_RES); + adc->ichaverage_res = da9030_get_reg(ICHAVERAGE_RES); + adc->vchmax_res = da9030_get_reg(VCHMAX_RES); + adc->vchmin_res = da9030_get_reg(VCHMIN_RES); + adc->tbat_res = da9030_get_reg(TBAT_RES); + adc->adc_in4_res = da9030_get_reg(ADC_IN4_RES); + adc->adc_in5_res = da9030_get_reg(ADC_IN5_RES); + + mutex_unlock(&the_da9030->lock); +} +EXPORT_SYMBOL(da9030_read_adc); + +void da9030_enable_adc(void) +{ + /* enable automatic A/D measurements */ + mutex_lock(&the_da9030->lock); + + da9030_set_reg(ADC_MAN_CONTROL, + ADC_LDO_INT_ENABLE | ADC_TBATREF_ENABLE); + da9030_set_reg(ADC_MAN_CONTROL_1, + ADC_LDO_INT_ENABLE | ADC_TBATREF_ENABLE); + da9030_set_reg(ADC_AUTO_CONTROL_1, + ADC_TBAT_ENABLE | ADC_VBAT_IN_TXON | ADC_VCH_ENABLE | + ADC_ICH_ENABLE | ADC_VBAT_ENABLE | + ADC_AUTO_SLEEP_ENABLE); + da9030_set_reg(ADC_AUTO_CONTROL, + ADC_TBAT_ENABLE | ADC_VBAT_IN_TXON | ADC_VCH_ENABLE | + ADC_ICH_ENABLE | ADC_VBAT_ENABLE | + ADC_AUTO_SLEEP_ENABLE); + + mutex_unlock(&the_da9030->lock); +} +EXPORT_SYMBOL(da9030_enable_adc); + +void da9030_set_wled(int on, unsigned int brightness) +{ + u8 val; + + mutex_lock(&the_da9030->lock); + + if (on) + val = WLED_CP_ENABLE | (brightness & 0x7); + else + val = da9030_get_reg(WLED_CONTROL) & ~WLED_CP_ENABLE; + + da9030_set_reg(WLED_CONTROL, val); + + mutex_unlock(&the_da9030->lock); +} +EXPORT_SYMBOL(da9030_set_wled); + +int da9030_set_charger(int on, unsigned int mA, unsigned int mV) +{ + int ret; + u8 val = 0; + if (on) { + if (mA >= 1500 || mV < 4000 || mV > 4350) + return -EINVAL; + + val = CHRG_CHARGER_ENABLE; + val |= (mA / 100) << 3; + val |= (mV - 4000) / 50; + } + + mutex_lock(&the_da9030->lock); + ret = da9030_set_reg(CHARGE_CONTROL, val); + mutex_unlock(&the_da9030->lock); + + return ret; +} +EXPORT_SYMBOL(da9030_set_charger); + +void da9030_get_charger(int *on, unsigned int *mA, unsigned int *mV) +{ + s32 val; + + mutex_lock(&the_da9030->lock); + + val = da9030_get_reg(CHARGE_CONTROL); + + mutex_unlock(&the_da9030->lock); + + if (on) + *on = (val & CHRG_CHARGER_ENABLE) ? 1 : 0; + + if (mA) + *mA = ((val >> 3) & 0xf) * 100; + + if (mV) + *mV = (val & 0x7) * 50 + 4000; +} +EXPORT_SYMBOL(da9030_get_charger); + +int da9030_set_led(int led, int on, + enum da9030_led_rate rate, + enum da9030_led_duty_cycle duty, + enum da9030_led_pwm_chop pwm_chop) +{ + int reg; + int ret; + + u8 val = 0; + + if (led > 4) + return -EINVAL; + + reg = LED_1_CONTROL + led; + if (on) { + val = LED_ENABLE; + val |= (rate & 0x3) << 5; + val |= (duty & 0x3) << 3; + val |= (pwm_chop & 0x7); + } + + mutex_lock(&the_da9030->lock); + ret = da9030_set_reg(reg, val); + mutex_unlock(&the_da9030->lock); + + return ret; +} +EXPORT_SYMBOL(da9030_set_led); + +void da9030_set_thresholds(unsigned int tbathighp, unsigned int tbathighn, + unsigned int tbatlow, unsigned int vbatmon) +{ + mutex_lock(&the_da9030->lock); + + da9030_set_reg(TBATHIGHP, tbathighp); + da9030_set_reg(TBATHIGHN, tbathighn); + da9030_set_reg(TBATLOW, tbatlow); + da9030_set_reg(VBATMONTXON, vbatmon); + da9030_set_reg(VBATMON, vbatmon); + + da9030_set_reg(TBATHIGHP_1, tbathighp); + da9030_set_reg(TBATHIGHN_1, tbathighn); + da9030_set_reg(TBATLOW_1, tbatlow); + da9030_set_reg(VBATMONTXMON_1, vbatmon); + da9030_set_reg(VBATMON_1, vbatmon); + + mutex_unlock(&the_da9030->lock); +} +EXPORT_SYMBOL(da9030_set_thresholds); + +struct da9030_vtg_value { + u16 vtg; + u16 val; +}; + +struct da9030_vtg_value da9030_vtg_1V8_3V2[] = { + {1800, 0x0}, + {1900, 0x1}, + {2000, 0x2}, + {2100, 0x3}, + {2200, 0x4}, + {2300, 0x5}, + {2400, 0x6}, + {2500, 0x7}, + {2600, 0x8}, + {2700, 0x9}, + {2800, 0xa}, + {2900, 0xb}, + {3000, 0xc}, + {3100, 0xd}, + {3200, 0xe}, + {3200, 0xf}, +}; + +struct da9030_vtg_value da9030_vtg_1V1_2V65[] = { + {1100, 0x0}, + {1150, 0x1}, + {1200, 0x2}, + {1250, 0x3}, + {1300, 0x4}, + {1350, 0x5}, + {1400, 0x6}, + {1450, 0x7}, + {1500, 0x8}, + {1550, 0x9}, + {1600, 0xa}, + {1650, 0xb}, + {1700, 0xc}, + {1750, 0xd}, + {1800, 0xe}, + {1850, 0xf}, + {1900, 0x10}, + {1950, 0x11}, + {2000, 0x12}, + {2050, 0x13}, + {2100, 0x14}, + {2150, 0x15}, + {2200, 0x16}, + {2250, 0x17}, + {2300, 0x18}, + {2350, 0x19}, + {2400, 0x1a}, + {2450, 0x1b}, + {2500, 0x1c}, + {2550, 0x1d}, + {2600, 0x1e}, + {2650, 0x1f}, +}; + +struct da9030_vtg_value da9030_vtg_2V76_2V94[] = { + {2760, 0x7}, + {2790, 0x6}, + {2820, 0x5}, + {2850, 0x4}, + {2850, 0x3}, + {2880, 0x1}, + {2910, 0x2}, + {2940, 0x3}, +}; + +struct da9030_vtg_value da9030_vtg_0V85_1V625[] = { + {850, 0x0}, + {875, 0x1}, + {900, 0x2}, + {925, 0x3}, + {950, 0x4}, + {975, 0x5}, + {1000, 0x6}, + {1025, 0x7}, + {1050, 0x8}, + {1075, 0x9}, + {1100, 0xa}, + {1125, 0xb}, + {1150, 0xc}, + {1175, 0xd}, + {1200, 0xe}, + {1225, 0xf}, + {1250, 0x10}, + {1275, 0x11}, + {1300, 0x12}, + {1325, 0x13}, + {1350, 0x14}, + {1375, 0x15}, + {1400, 0x16}, + {1425, 0x17}, + {1450, 0x18}, + {1475, 0x19}, + {1500, 0x1a}, + {1525, 0x1b}, + {1550, 0x1c}, + {1575, 0x1d}, + {1600, 0x1e}, + {1625, 0x1f}, +}; + +struct ldo_param { + u8 reg; + u8 shift; + u8 bits; +}; + +struct da9030_ldo { + const char *name; + + struct ldo_param vtg; + struct ldo_param sleep; + struct ldo_param lock; + + /* several LDOs have two enable/disable bits */ + struct ldo_param enable[2]; + + struct da9030_vtg_value *values; + int values_count; +}; + +static struct da9030_ldo da9030_ldos[] = { + [0] = { + .name = "LDO1", + .vtg = { + .reg = LDO_1, + .shift = 0, + .bits = 5, + }, + .enable[0] = { + .reg = REG_CONTROL_1_97, + .shift = 1, + .bits = 1, + }, + .sleep = { + .reg = LDO_1, + .shift = 5, + .bits = 2, + }, + .lock = { + .reg = REG_SLEEP_CONTROL1, + .shift = 0, + .bits = 2, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [1] = { + .name = "LDO2", + .vtg = { + .reg = LDO_2_3, + .shift = 0, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_1_97, + .shift = 2, + .bits = 1, + }, + .sleep = { + .reg = REG_SLEEP_CONTROL1, + .shift = 2, + .bits = 2, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [2] = { + .name = "LDO3", + .vtg = { + .reg = LDO_2_3, + .shift = 4, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_1_97, + .shift = 3, + .bits = 1, + }, + .sleep = { + .reg = REG_SLEEP_CONTROL1, + .shift = 4, + .bits = 2, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [3] = { + .name = "LDO4", + .vtg = { + .reg = LDO_4_5, + .shift = 0, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_1_97, + .shift = 4, + .bits = 1, + }, + .sleep = { + .reg = REG_SLEEP_CONTROL1, + .shift = 6, + .bits = 2, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [4] = { + .name = "LDO5", + .vtg = { + .reg = LDO_4_5, + .shift = 4, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_1_97, + .shift = 5, + .bits = 1, + }, + .sleep = { + .reg = REG_SLEEP_CONTROL2, + .shift = 0, + .bits = 2, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [5] = { + .name = "LDO6", + .vtg = { + .reg = LDO_6_SIMCP, + .shift = 0, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_1_97, + .shift = 6, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [6] = { + .name = "LDO7", + .vtg = { + .reg = LDO_7_8, + .shift = 0, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_1_97, + .shift = 7, + .bits = 1, + }, + .sleep = { + .reg = REG_SLEEP_CONTROL2, + .shift = 2, + .bits = 2, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [7] = { + .name = "LDO8", + .vtg = { + .reg = LDO_7_8, + .shift = 4, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_2_98, + .shift = 0, + .bits = 1, + }, + .sleep = { + .reg = REG_SLEEP_CONTROL2, + .shift = 4, + .bits = 2, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [8] = { + .name = "LDO9", + .vtg = { + .reg = LDO_9_12, + .shift = 0, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_2_98, + .shift = 1, + .bits = 1, + }, + .sleep = { + .reg = REG_SLEEP_CONTROL2, + .shift = 6, + .bits = 2, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [9] = { + .name = "LDO10", + .vtg = { + .reg = LDO_10_11, + .shift = 0, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_1_17, + .shift = 1, + .bits = 1, + }, + .enable[1] = { + .reg = REG_CONTROL_2_98, + .shift = 2, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [10] = { + .name = "LDO11", + .vtg = { + .reg = LDO_10_11, + .shift = 4, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_1_17, + .shift = 2, + .bits = 1, + }, + .enable[1] = { + .reg = REG_CONTROL_2_98, + .shift = 3, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [11] = { + .name = "LDO12", + .vtg = { + .reg = LDO_9_12, + .shift = 4, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_2_98, + .shift = 4, + .bits = 1, + }, + .sleep = { + .reg = REG_SLEEP_CONTROL3, + .shift = 0, + .bits = 2, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [12] = { + .name = "LDO13", + .vtg = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .enable[0] = { + .reg = REG_CONTROL_1_17, + .shift = 3, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .values = NULL, + .values_count = 0, + }, + [13] = { + .name = "LDO14", + .vtg = { + .reg = LDO_14_16, + .shift = 0, + .bits = 3, + }, + .enable[0] = { + .reg = REG_CONTROL_1_17, /* FIXME: or 2_98? */ + .shift = 4, + .bits = 1, + }, + .enable[1] = { + .reg = REG_CONTROL_2_98, /* FIXME: or 2_98? */ + .shift = 5, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .values = da9030_vtg_2V76_2V94, + .values_count = ARRAY_SIZE(da9030_vtg_2V76_2V94), + }, + [14] = { + .name = "LDO15", + .vtg = { + .reg = LDO_15, + .shift = 0, + .bits = 5, + }, + .enable[0] = { + .reg = REG_CONTROL_1_17, + .shift = 5, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .lock = { + .reg = LDO_15, + .shift = 5, + .bits = 3, + }, + .values = da9030_vtg_1V1_2V65, + .values_count = ARRAY_SIZE(da9030_vtg_1V1_2V65), + }, + [15] = { + .name = "LDO16", + .vtg = { + .reg = LDO_14_16, + .shift = 3, + .bits = 5, + }, + .enable[0] = { + .reg = REG_CONTROL_1_17, + .shift = 6, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .values = da9030_vtg_1V1_2V65, + .values_count = ARRAY_SIZE(da9030_vtg_1V1_2V65), + }, + [16] = { + .name = "LDO17", + .vtg = { + .reg = LDO_17_SIMCP0, + .shift = 0, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_1_17, + .shift = 7, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [17] = { + .name = "LDO18", + .vtg = { + .reg = LDO_18_19, + .shift = 0, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_2_18, + .shift = 2, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, + [18] = { + .name = "LDO19", + .vtg = { + .reg = LDO_18_19, + .shift = 4, + .bits = 4, + }, + .enable[0] = { + .reg = REG_CONTROL_2_18, + .shift = 1, + .bits = 1, + }, + .sleep = { + .reg = 0, + .shift = 0, + .bits = 0, + }, + .values = da9030_vtg_1V8_3V2, + .values_count = ARRAY_SIZE(da9030_vtg_1V8_3V2), + }, +}; + +static int da9030_get_vtg_value(int vtg, const struct da9030_vtg_value *tbl, + int n) +{ + int i; + + for (i = 0; i < n; i++, tbl++) + if (tbl->vtg == vtg) + return tbl->val; + return -EINVAL; +} + +static int da9030_set_ldo_volt(struct da9030_ldo *ldo, unsigned int mV) +{ + int val; + + val = da9030_get_vtg_value(mV, ldo->values, ldo->values_count); + if (val < 0) + return val; + + return da9030_update_reg_bits(ldo->vtg.reg, ldo->vtg.bits, + ldo->vtg.shift, val); +} + +static int da9030_enable_ldo(struct da9030_ldo *ldo, int enable) +{ + int ret; + + ret = da9030_update_reg_bits(ldo->enable[0].reg, ldo->enable[1].bits, + ldo->enable[0].shift, enable); + + if (ret < 0) + return ret; + + if (unlikely(ldo->enable[1].reg != 0)) + ret = da9030_update_reg_bits(ldo->enable[1].reg, + ldo->enable[1].bits, + ldo->enable[1].shift, enable); + + return ret; +} + +static int da9030_set_ldo_sleep(struct da9030_ldo *ldo, + enum da9030_ldo_sleep_mode sleep_mode) +{ + return da9030_update_reg_bits(ldo->sleep.reg, ldo->sleep.bits, + ldo->sleep.shift, sleep_mode); +} + +int da9030_set_ldo(int ldo_num, int on, unsigned int mV, + enum da9030_ldo_sleep_mode sleep_mode) +{ + struct da9030_ldo *ldo; + int ret; + + if (ldo_num < 0 || ldo_num > ARRAY_SIZE(da9030_ldos)) + return -EINVAL; + + ldo = &da9030_ldos[ldo_num]; + + ret = da9030_set_ldo_volt(ldo, mV); + if (ret < 0) + return ret; + + ret = da9030_enable_ldo(ldo, on); + if (ret < 0) + return ret; + + ret = da9030_set_ldo_sleep(ldo, sleep_mode); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(da9030_set_ldo); + +int da9030_set_buck(int buck, int on, unsigned int mV, int flags) +{ + /* FIXME: implement */ + return 0; +} +EXPORT_SYMBOL(da9030_set_buck); + +static void da9030_disable_irq(unsigned int irq) +{ + int reg, val; + + if (irq < 8) { + reg = IRQ_MASK_A; + val = 1 << irq; + } else if (irq < 16) { + reg = IRQ_MASK_B; + val = 1 << (irq - 8); + } else { + reg = IRQ_MASK_C; + val = 1 << (irq - 16); + } + + i2c_smbus_write_byte_data( + the_da9030->client, reg, + i2c_smbus_read_byte_data(the_da9030->client, reg) | val); +} + +static void da9030_enable_irq(unsigned int irq) +{ + int reg, val; + + if (irq < 8) { + reg = IRQ_MASK_A; + val = 1 << irq; + } else if (irq < 16) { + reg = IRQ_MASK_B; + val = 1 << (irq - 8); + } else { + reg = IRQ_MASK_C; + val = 1 << (irq - 16); + } + + i2c_smbus_write_byte_data( + the_da9030->client, reg, + i2c_smbus_read_byte_data(the_da9030->client, reg) & ~val); +} + +int da9030_register_callback(int event, + void (*callback)(int event, void *data), + void *data) +{ + int ret; + + if (event < 0 || event > 23) + return -EINVAL; + + mutex_lock(&the_da9030->lock); + if (the_da9030->callbacks[event]) + ret = -EBUSY; + else { + the_da9030->callbacks[event] = callback; + the_da9030->callbacks_data[event] = data; + da9030_enable_irq(event); + ret = 0; + } + mutex_unlock(&the_da9030->lock); + + return ret; +} +EXPORT_SYMBOL(da9030_register_callback); + +void da9030_unregister_callback(int event) +{ + mutex_lock(&the_da9030->lock); + + da9030_disable_irq(event); + the_da9030->callbacks[event] = NULL; + the_da9030->callbacks_data[event] = 0; + + mutex_unlock(&the_da9030->lock); +} +EXPORT_SYMBOL(da9030_unregister_callback); + +#ifdef CONFIG_DEBUG_FS +#define MAX_BUF 256 + +static int da9030_debug_show(struct seq_file *s, void *data) +{ + int i, res = 0; + struct da9030 *da9030 = s->private; + struct i2c_client *client = da9030->client; + + seq_printf(s, "DA9030 state: da = %p, cl = %p, s->private = %p\n", + da9030, client, s->private); + + for (i = 0; i < ARRAY_SIZE(defined_regs); i++) { + res = i2c_smbus_read_byte_data(client, defined_regs[i]); + seq_printf(s, "%02x %x\n", defined_regs[i], res); + } + return 0; +} + +static int da9030_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, da9030_debug_show, inode->i_private); +} + +ssize_t da9030_debug_write(struct file *f, const char __user *buf, size_t len, + loff_t *off) +{ + char buffer[MAX_BUF]; + int reg, val; + char *endp; + struct da9030 *da9030 = ((struct seq_file *)f->private_data)->private; + struct i2c_client *client = da9030->client; + + if (len > MAX_BUF) { + printk(KERN_INFO "%s: large buffer\n", __FUNCTION__); + len = MAX_BUF; + } + + if (copy_from_user(buffer, buf, len)) { + printk(KERN_INFO "%s: copy_from_user failed\n", __FUNCTION__); + return -EFAULT; + } + buffer[len] = '\0'; + + reg = simple_strtoul(buffer, &endp, 0); + while (endp && isspace(*endp)) + endp++; + + val = simple_strtoul(endp, 0, 0); + + i2c_smbus_write_byte_data(client, reg, val); + + return len; +} + +static const struct file_operations debug_fops = { + .open = da9030_debug_open, + .read = seq_read, + .write = da9030_debug_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *da9030_create_debugfs(struct da9030 *da9030) +{ + da9030->debug_file = debugfs_create_file("da9030", 0666, 0, da9030, + &debug_fops); + return da9030->debug_file; +} + +static void da9030_remove_debugfs(struct da9030 *da9030) +{ + debugfs_remove(da9030->debug_file); +} +#else +#define da9030_create_debugfs(x) NULL +#define da9030_remove_debugfs(x) do {} while (0) +#endif + +static irqreturn_t da9030_irq(int irq, void *_da9030) +{ + struct da9030 *da9030 = _da9030; + + (void)schedule_work(&da9030->event_work); + + return IRQ_HANDLED; +} + +static void da9030_irq_worker(struct work_struct *work) +{ + struct da9030 *da9030 = container_of(work, struct da9030, event_work); + void (*callback)(int event, void *data); + u32 pending = 0; + u32 mask = 0; + int i; + + while (1) { + pending = (i2c_smbus_read_byte_data(da9030->client, + EVENT_A)) | + ((i2c_smbus_read_byte_data(da9030->client, + EVENT_B)) << 8) | + ((i2c_smbus_read_byte_data(da9030->client, + EVENT_C)) << 16); + + mask = (i2c_smbus_read_byte_data(da9030->client, + IRQ_MASK_A)) | + ((i2c_smbus_read_byte_data(da9030->client, + IRQ_MASK_B)) << 8) | + ((i2c_smbus_read_byte_data(da9030->client, + IRQ_MASK_C)) << 16); + pending &= ~mask; + + if (!pending) + return; + + while (pending) { + i = __ffs(pending); + callback = da9030->callbacks[i]; + if (callback) + callback(i, da9030->callbacks_data[i]); + pending &= ~(1 << i); + } + } +} + +static inline void da9030_disable_interrupts(struct da9030 *da9030) +{ + /* clear pending interruts */ + (void)i2c_smbus_read_byte_data(da9030->client, EVENT_A); + (void)i2c_smbus_read_byte_data(da9030->client, EVENT_B); + (void)i2c_smbus_read_byte_data(da9030->client, EVENT_C); + + /* disable interrupts */ + i2c_smbus_write_byte_data(da9030->client, IRQ_MASK_A, 0xff); + i2c_smbus_write_byte_data(da9030->client, IRQ_MASK_B, 0xff); + i2c_smbus_write_byte_data(da9030->client, IRQ_MASK_C, 0xff); +} + +static int da9030_probe(struct i2c_client *client) +{ + int ret; + struct da9030 *da9030; + + if (the_da9030) { + dev_dbg(&client->dev, "only one %s for now\n", + DRIVER_NAME); + return -ENODEV; + } + + ret = i2c_smbus_read_byte_data(client, CHIP_ID); + + dev_info(&client->dev, "initialized chip revision %x\n", ret); + + da9030 = kzalloc(sizeof(struct da9030), GFP_KERNEL); + if (da9030 == NULL) { + dev_err(&client->dev, "insufficient memory\n"); + return -ENOMEM; + } + the_da9030 = da9030; + da9030->client = client; + + mutex_init(&the_da9030->lock); + + da9030_disable_interrupts(da9030); + INIT_WORK(&da9030->event_work, da9030_irq_worker); + + ret = request_irq(client->irq, da9030_irq, + IRQF_DISABLED, DRIVER_NAME, da9030); + + if (ret) { + kfree(da9030); + dev_err(&client->dev, + "failed to allocate irq %d\n", + client->irq); + return ret; + } + + i2c_set_clientdata(client, da9030); + + da9030->debug_file = da9030_create_debugfs(da9030); + + return 0; +} + +/* static int da9030_detach_adapter(struct i2c_adapter *a) */ +static int da9030_remove(struct i2c_client *client) +{ + struct da9030 *da9030 = i2c_get_clientdata(client); + + da9030_remove_debugfs(da9030); + + free_irq(da9030->client->irq, da9030); + kfree(da9030); + i2c_set_clientdata(client, NULL); + the_da9030 = NULL; + + return 0; +} + +static struct i2c_driver da9030_driver = { + .driver = { + .name = "da9030", + .owner = THIS_MODULE, + }, + + .probe = da9030_probe, + .remove = da9030_remove, +}; + +static int da9030_init(void) +{ + i2c_add_driver(&da9030_driver); + return 0; +} + +static void da9030_exit(void) +{ + i2c_del_driver(&da9030_driver); +} + +/* NOTE: this MUST be initialized before the other parts of the system + * that rely on it ... but after the i2c bus on which this relies. + * That is, much earlier than on PC-type systems, which don't often use + * I2C as a core system bus. + */ +subsys_initcall(da9030_init); +module_exit(da9030_exit); + +MODULE_DESCRIPTION("DA9030 power manager driver"); +MODULE_AUTHOR("Mike Rapoport, Compulab"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/chips/da9030.h b/drivers/i2c/chips/da9030.h new file mode 100644 index 0000000..4163156 --- /dev/null +++ b/drivers/i2c/chips/da9030.h @@ -0,0 +1,282 @@ +/* DA9030 Register definintions */ + +#define CHIP_ID 0x00 + +#define EVENT_A 0x01 +#define EVENT_A_CHIOVER (1 << 7) +#define EVENT_A_VBAT_MON_TXON (1 << 6) +#define EVENT_A_VBAT_MON (1 << 5) +#define EVENT_A_TBAT (1 << 4) +#define EVENT_A_CHDET (1 << 3) +#define EVENT_A_EXTON (1 << 2) +#define EVENT_A_PWREN1 (1 << 1) +#define EVENT_A_ONKEY_N (1 << 0) + +#define EVENT_B 0x02 +#define EVENT_B_WDOG_INT (1 << 7) +#define EVENT_B_SRP_DETECT (1 << 6) +#define EVENT_B_SESSION_VALID (1 << 5) +#define EVENT_B_VBUS_VALID_4_0 (1 << 4) +#define EVENT_B_VBUS_VALID_4_4 (1 << 3) +#define EVENT_B_ADC_READY (1 << 2) +#define EVENT_B_CCTO (1 << 1) +#define EVENT_B_TCTO (1 << 0) + +#define EVENT_C 0x03 +#define EVENT_C_ADC_IN5 (1 << 7) +#define EVENT_C_ADC_IN4 (1 << 6) +#define EVENT_C_BUCK2 (1 << 5) +#define EVENT_C_LDO19 (1 << 4) +#define EVENT_C_LDO18 (1 << 3) +#define EVENT_C_LDO17 (1 << 2) +#define EVENT_C_LDO16 (1 << 1) +#define EVENT_C_LDO15 (1 << 0) + +#define STATUS 0x04 +#define STATUS_MCLK_DETECT (1 << 7) +#define STATUS_VBAT_MON_TXON (1 << 6) +#define STATUS_VBAT_MON (1 << 5) +#define STATUS_TBAT (1 << 4) +#define STATUS_CHDET (1 << 3) +#define STATUS_EXTON (1 << 2) +#define STATUS_PWREN1 (1 << 1) +#define STATUS_ONKEY_N (1 << 0) + +#define IRQ_MASK_A 0x05 +#define IRQ_MASK_A_CHIOVER (1 << 7) +#define IRQ_MASK_A_VBAT_MON_TXON (1 << 6) +#define IRQ_MASK_A_VBAT_MON (1 << 5) +#define IRQ_MASK_A_TBAT (1 << 4) +#define IRQ_MASK_A_CHDET (1 << 3) +#define IRQ_MASK_A_EXTON (1 << 2) +#define IRQ_MASK_A_PWREN1 (1 << 1) +#define IRQ_MASK_A_ONKEY_N (1 << 0) + +#define IRQ_MASK_B 0x06 +#define IRQ_MASK_B_WDOG_INT (1 << 7) +#define IRQ_MASK_B_SRP_DETECT (1 << 6) +#define IRQ_MASK_B_SESSION_VALID (1 << 5) +#define IRQ_MASK_B_VBUS_VALID_4_0 (1 << 4) +#define IRQ_MASK_B_VBUS_VALID_4_4 (1 << 3) +#define IRQ_MASK_B_ADC_READY (1 << 2) +#define IRQ_MASK_B_CCTO (1 << 1) +#define IRQ_MASK_B_TCTO (1 << 0) + +#define IRQ_MASK_C 0x07 +#define IRQ_MASK_C_ADC_IN5 (1 << 7) +#define IRQ_MASK_C_ADC_IN4 (1 << 6) +#define IRQ_MASK_C_BUCK2 (1 << 5) +#define IRQ_MASK_C_LDO19 (1 << 4) +#define IRQ_MASK_C_LDO18 (1 << 3) +#define IRQ_MASK_C_LDO17 (1 << 2) +#define IRQ_MASK_C_LDO16 (1 << 1) +#define IRQ_MASK_C_LDO15 (1 << 0) + +#define SYS_CTRL_A 0x08 +#define SYS_CONTROL_A_SLEEP_N_PIN_ENABLE 0x1 +#define SYS_CONTROL_A_SHUT_DOWN (1<<1) +#define SYS_CONTROL_A_HWRES_ENABLE (1<<2) +#define SYS_CONTROL_A_WDOG_ACTION (1<<3) +#define SYS_CONTROL_A_WATCHDOG (1<<7) + +#define SYS_CONTROL_B 0x09 +#define SYS_CLTR_B_SLEEP_13MHZ_EN (1 << 4) +#define SYS_CLTR_B_AUTO_CLK_SWITCH (1 << 3) + +#define FAULT_LOG 0x0a +#define FAULT_LOG_OVER_TEMP (1 << 7) +#define FAULT_LOG_VBAT_OVER (1 << 4) + +#define LDO_10_11 0x10 +#define LDO_10_11_LDO10_3V (0xc) +#define LDO_10_11_LDO11_3V2 (0xe << 4) + +#define LDO_15 0x11 +#define LDO_14_16 0x12 +#define LDO_18_19 0x13 +#define LDO_18_19_LDO18_3V2 (0xe) + +#define LDO_17_SIMCP0 0x14 +#define LDO_17_SIMCP0_LDO17_3V 0x0F + +#define BUCK_2_DVC1 0x15 +#define BUCK_2_GO (1 << 7) +#define BUCK_2_SLEEP (1 << 6) +#define BUCK_2_TRIM_1V5 (0x1a) + +#define BUCK_2_DVC2 0x16 + +#define REG_CONTROL_1_17 0x17 +#define RC1_LDO17_EN (1 << 7) +#define RC1_LDO16_EN (1 << 6) +#define RC1_LDO15_EN (1 << 5) +#define RC1_LDO14_EN (1 << 4) +#define RC1_LDO13_EN (1 << 3) +#define RC1_LDO11_EN (1 << 2) +#define RC1_LDO10_EN (1 << 1) +#define RC1_BUCK2_EN (1 << 0) + +#define REG_CONTROL_2_18 0x18 +#define RC2_SIMCP_EN (1 << 6) +#define RC2_LDO19_EN (1 << 1) +#define RC2_LDO18_EN (1 << 0) + +#define REG_CONTROL_3_ + +#define USBPUMP 0x19 +#define USB_PUMP_EN_USBVEP (1 << 7) +#define USB_PUMP_EN_USBVE (1 << 6) +#define USB_PUMP_SPR_DETECT (1 << 5) +#define USB_PUMP_SESSION_VALID (1 << 4) +#define USB_PUMP_VBUS_VALID_4_0 (1 << 3) +#define USB_PUMP_VBUS_VALID_4_4 (1 << 2) +#define USB_PUMP_USBVEP (1 << 1) +#define USB_PUMP_USBVE (1 << 0) + +#define SLEEP_CONTROL 0x1a +#define APP_SLEEP_CTL_PWR_EN (1 << 7) +#define APP_SLEEP_CTL_SYS_EN (1 << 6) +#define APP_SLEEP_CTL_BYPASS_LDO19 (1 << 2) +#define APP_SLEEP_CTL_BYPASS_LDO18 (1 << 1) +#define APP_SLEEP_CTL_BYPASS_LDO17 (1 << 0) + +#define STARTUP_CONTROL 0x1b +#define STARTUP_CTL_LDO11_PWR_SYS (1 << 3) +#define STARTUP_CTL_LDO10_PWR_SYS (1 << 2) +#define STARTUP_CTL_LDO11_START (1 << 1) +#define STARTUP_CTL_LDO10_START (1 << 0) + +#define LED_1_CONTROL 0x20 +#define LED_2_CONTROL 0x21 +#define LED_3_CONTROL 0x22 +#define LED_4_CONTROL 0x23 +#define LEDPC_CONTROL 0x24 +#define LED_ENABLE (1 << 7) + +#define WLED_CONTROL 0x25 +#define WLED_CP_ENABLE (1 << 6) +#define WLED_ISET_10 (3) + +#define MISC_CONTROLA 0x26 +#define MISC_CONTROLB 0x27 +#define MISCB_SESSION_VALID_ENABLE (1 << 3) +#define MISCB_USB_INT_RISING (1 << 2) +#define MISCB_I2C_ADDRESS (1 << 1) +#define MISCB_STARTUP_SEQUENCE (1 << 0) + +#define CHARGE_CONTROL 0x28 +#define CHRG_CHARGER_ENABLE (1 << 7) + +#define CCTR_CONTROL 0x29 +#define CCTR_SET_8MIN 0x01 + +#define TCTR_CONTROL 0x2a +#define CHARGE_PULSE 0x2b + +#define ADC_MAN_CONTROL 0x30 + +#define ADC_AUTO_CONTROL 0x31 +#define ADC_IN5_EN (1 << 7) +#define ADC_IN4_EN (1 << 6) +#define ADC_TBAT_ENABLE (1 << 5) +#define ADC_VBAT_IN_TXON (1 << 4) +#define ADC_VCH_ENABLE (1 << 3) +#define ADC_ICH_ENABLE (1 << 2) +#define ADC_VBAT_ENABLE (1 << 1) +#define ADC_AUTO_SLEEP_ENABLE (1 << 0) + +#define VBATMON 0x32 +#define VBATMONTXON 0x33 +#define TBATHIGHP 0x34 +#define TBATHIGHN 0x35 +#define TBATLOW 0x36 +#define ADC_IN4_MIN 0x37 +#define ADC_IN4_MAX 0x38 +#define ADC_IN5_MIN 0x39 +#define ADC_IN5_MAX 0x3a + +#define VBAT_RES 0x41 +#define VBATMIN_RES 0x42 +#define VBATMINTXON 0x43 +#define ICHMAX_RES 0x44 +#define ICHMIN_RES 0x45 +#define ICHAVERAGE_RES 0x46 +#define VCHMAX_RES 0x47 +#define VCHMIN_RES 0x48 +#define TBAT_RES 0x49 +#define ADC_IN4_RES 0x4a +#define ADC_IN5_RES 0x4b + +#define LDO_1 0x90 +#define LDO_1_UNLOCK (0x5 << 5) +#define LDO_1_TRIM_3V (0x12) + +#define LDO_2_3 0x91 +#define LDO_2_3_LDO2_3V2 (0xe << 4) +#define LDO_2_3_LDO3_3V (0xc) + +#define LDO_4_5 0x92 +#define LDO_6_SIMCP 0x93 +#define LDO_6_SIMCP_LDO6_3V2 (0xe) + +#define LDO_7_8 0x94 +#define LDO_9_12 0x95 +#define BUCK 0x96 +#define REG_CONTROL_1_97 0x97 +#define RC3_LDO7_EN (1 << 7) +#define RC3_LDO6_EN (1 << 6) +#define RC3_LDO5_EN (1 << 5) +#define RC3_LDO4_EN (1 << 4) +#define RC3_LDO3_EN (1 << 3) +#define RC3_LDO2_EN (1 << 2) +#define RC3_LDO1_EN (1 << 1) +#define RC3_BUCK_EN (1 << 0) + +#define REG_CONTROL_2_98 0x98 +#define RC4_SLEEP (1 << 7) +#define RC4_SIMCP_ENABLE (1 << 6) +#define RC4_LDO14_EN (1 << 5) +#define RC4_LDO12_EN (1 << 4) +#define RC4_LDO11_EN (1 << 3) +#define RC4_LDO10_EN (1 << 2) +#define RC4_LDO9_EN (1 << 1) +#define RC4_LDO8_EN (1 << 0) + +#define REG_SLEEP_CONTROL1 0x99 +#define REG_SLEEP_CONTROL2 0x9a +#define REG_SLEEP_CONTROL3 0x9b + +#define ADC_MAN_CONTROL_1 0xa0 +#define ADC_DEBOUNCE_VBATMON_TXON (1 << 7) +#define ADC_DEBOUNCE_VBATMON (1 << 6) +#define ADC_TBATREF_ENABLE (1 << 5) +#define ADC_LDO_INT_ENABLE (1 << 4) +#define ADC_MAN_CONV (1 << 3) +#define ADC_MUX_VBAT (0) + +#define ADC_AUTO_CONTROL_1 0xa1 +#define ADC_IN5_EN (1 << 7) +#define ADC_IN4_EN (1 << 6) +#define ADC_TBAT_ENABLE (1 << 5) +#define ADC_VBAT_IN_TXON (1 << 4) +#define ADC_VCH_ENABLE (1 << 3) +#define ADC_ICH_ENABLE (1 << 2) +#define ADC_VBAT_ENABLE (1 << 1) +#define ADC_AUTO_SLEEP_ENABLE (1 << 0) + +#define VBATMON_1 0xa2 +#define VBATMONTXMON_1 0xa3 +#define TBATHIGHP_1 0xa4 +#define TBATHIGHN_1 0xa5 +#define TBATLOW_1 0xa6 +#define MAN_RES 0xb0 +#define VBAT_RES_1 0xb1 +#define VBATMIN_RES_1 0xb2 +#define VBATMINTXON_RES 0xb3 +#define ICHMAX_RES_1 0xb4 +#define ICHMIN_RES_1 0xb5 +#define ICHAVERAGE_RES_1 0xb6 +#define VCHMAX_RES_1 0xb7 +#define VCHMIN_RES_1 0xb8 +#define TBAT_RES_1 0xb9 +#define ADC_IN4_RES_1 0xba diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index f929fcd..174ea2e 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -126,6 +126,49 @@ config TOUCHSCREEN_HP600 To compile this driver as a module, choose M here: the module will be called hp680_ts_input. +config TOUCHSCREEN_WM97XX + tristate "Support for WM97xx AC97 touchscreen controllers" + depends AC97_BUS + +choice + prompt "WM97xx codec type" + depends TOUCHSCREEN_WM97XX + +config TOUCHSCREEN_WM9705 + bool "WM9705 Touchscreen interface support" + depends on TOUCHSCREEN_WM97XX + help + Say Y here if you have the wm9705 touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called wm9705. + +config TOUCHSCREEN_WM9712 + bool "WM9712 Touchscreen interface support" + depends on TOUCHSCREEN_WM97XX + help + Say Y here if you have the wm9712 touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called wm9712. + +config TOUCHSCREEN_WM9713 + bool "WM9713 Touchscreen interface support" + depends on TOUCHSCREEN_WM97XX + help + Say Y here if you have the wm9713 touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called wm9713. + +endchoice + config TOUCHSCREEN_PENMOUNT tristate "Penmount serial touchscreen" select SERIO diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 5de8933..3a059b1 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -3,6 +3,7 @@ # # Each configuration option enables a list of files. +wm97xx-ts-objs := wm97xx-core.o obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o obj-$(CONFIG_TOUCHSCREEN_BITSY) += h3600_ts_input.o @@ -18,3 +19,16 @@ obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o +obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o + +ifeq ($(CONFIG_TOUCHSCREEN_WM9713),y) +wm97xx-ts-objs += wm9713.o +endif + +ifeq ($(CONFIG_TOUCHSCREEN_WM9712),y) +wm97xx-ts-objs += wm9712.o +endif + +ifeq ($(CONFIG_TOUCHSCREEN_WM9705),y) +wm97xx-ts-objs += wm9705.o +endif diff --git a/drivers/input/touchscreen/wm9705.c b/drivers/input/touchscreen/wm9705.c new file mode 100644 index 0000000..1dae63d --- /dev/null +++ b/drivers/input/touchscreen/wm9705.c @@ -0,0 +1,360 @@ +/* + * wm9705.c -- Codec driver for Wolfson WM9705 AC97 Codec. + * + * Copyright 2003, 2004, 2005, 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 6th Sep 2006 Mike Arthur <linux@wolfsonmicro.com> + * Added pre and post sample calls. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/wm97xx.h> + +#define TS_NAME "wm97xx" +#define WM9705_VERSION "0.62" +#define DEFAULT_PRESSURE 0xb0c0 + +/* + * Debug + */ +#if 0 +#define dbg(format, arg...) printk(KERN_DEBUG TS_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) +#endif +#define err(format, arg...) printk(KERN_ERR TS_NAME ": " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO TS_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING TS_NAME ": " format "\n" , ## arg) + +/* + * Module parameters + */ + +/* + * Set current used for pressure measurement. + * + * Set pil = 2 to use 400uA + * pil = 1 to use 200uA and + * pil = 0 to disable pressure measurement. + * + * This is used to increase the range of values returned by the adc + * when measureing touchpanel pressure. + */ +static int pil = 0; +module_param(pil, int, 0); +MODULE_PARM_DESC(pil, "Set current used for pressure measurement."); + +/* + * Set threshold for pressure measurement. + * + * Pen down pressure below threshold is ignored. + */ +static int pressure = DEFAULT_PRESSURE & 0xfff; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement."); + +/* + * Set adc sample delay. + * + * For accurate touchpanel measurements, some settling time may be + * required between the switch matrix applying a voltage across the + * touchpanel plate and the ADC sampling the signal. + * + * This delay can be set by setting delay = n, where n is the array + * position of the delay in the array delay_table below. + * Long delays > 1ms are supported for completeness, but are not + * recommended. + */ +static int delay = 4; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "Set adc sample delay."); + +/* + * Pen detect comparator threshold. + * + * 0 to Vmid in 15 steps, 0 = use zero power comparator with Vmid threshold + * i.e. 1 = Vmid/15 threshold + * 15 = Vmid/1 threshold + * + * Adjust this value if you are having problems with pen detect not + * detecting any down events. + */ +static int pdd = 8; +module_param(pdd, int, 0); +MODULE_PARM_DESC(pdd, "Set pen detect comparator threshold"); + +/* + * Set adc mask function. + * + * Sources of glitch noise, such as signals driving an LCD display, may feed + * through to the touch screen plates and affect measurement accuracy. In + * order to minimise this, a signal may be applied to the MASK pin to delay or + * synchronise the sampling. + * + * 0 = No delay or sync + * 1 = High on pin stops conversions + * 2 = Edge triggered, edge on pin delays conversion by delay param (above) + * 3 = Edge triggered, edge on pin starts conversion after delay param + */ +static int mask = 0; +module_param(mask, int, 0); +MODULE_PARM_DESC(mask, "Set adc mask function."); + +/* + * ADC sample delay times in uS + */ +static const int delay_table[] = { + 21, // 1 AC97 Link frames + 42, // 2 + 84, // 4 + 167, // 8 + 333, // 16 + 667, // 32 + 1000, // 48 + 1333, // 64 + 2000, // 96 + 2667, // 128 + 3333, // 160 + 4000, // 192 + 4667, // 224 + 5333, // 256 + 6000, // 288 + 0 // No delay, switch matrix always on +}; + +/* + * Delay after issuing a POLL command. + * + * The delay is 3 AC97 link frames + the touchpanel settling delay + */ +static inline void poll_delay(int d) +{ + udelay (3 * AC97_LINK_FRAME + delay_table [d]); +} + +/* + * set up the physical settings of the WM9705 + */ +static void init_wm9705_phy(struct wm97xx* wm) +{ + u16 dig1 = 0, dig2 = WM97XX_RPR; + + /* + * mute VIDEO and AUX as they share X and Y touchscreen + * inputs on the WM9705 + */ + wm97xx_reg_write(wm, AC97_AUX, 0x8000); + wm97xx_reg_write(wm, AC97_VIDEO, 0x8000); + + /* touchpanel pressure current*/ + if (pil == 2) { + dig2 |= WM9705_PIL; + dbg("setting pressure measurement current to 400uA."); + } else if (pil) + dbg("setting pressure measurement current to 200uA."); + if(!pil) + pressure = 0; + + /* polling mode sample settling delay */ + if (delay!=4) { + if (delay < 0 || delay > 15) { + dbg("supplied delay out of range."); + delay = 4; + } + } + dig1 &= 0xff0f; + dig1 |= WM97XX_DELAY(delay); + dbg("setting adc sample delay to %d u Secs.", delay_table[delay]); + + /* WM9705 pdd */ + dig2 |= (pdd & 0x000f); + dbg("setting pdd to Vmid/%d", 1 - (pdd & 0x000f)); + + /* mask */ + dig2 |= ((mask & 0x3) << 4); + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); +} + +static int wm9705_digitiser_ioctl(struct wm97xx* wm, int cmd) +{ + switch(cmd) { + case WM97XX_DIG_START: + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig[2] | WM97XX_PRP_DET_DIG); + wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */ + break; + case WM97XX_DIG_STOP: + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig[2] & ~WM97XX_PRP_DET_DIG); + break; + case WM97XX_AUX_PREPARE: + memcpy(wm->dig_save, wm->dig, sizeof(wm->dig)); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG); + break; + case WM97XX_DIG_RESTORE: + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]); + break; + case WM97XX_PHY_INIT: + init_wm9705_phy(wm); + break; + default: + return -EINVAL; + } + return 0; +} + +static inline int is_pden (struct wm97xx* wm) +{ + return wm->dig[2] & WM9705_PDEN; +} + +/* + * Read a sample from the WM9705 adc in polling mode. + */ +static int wm9705_poll_sample (struct wm97xx* wm, int adcsel, int *sample) +{ + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (adcsel & 0x8000) + adcsel = ((adcsel & 0x7fff) + 3) << 12; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(adcsel); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, adcsel | WM97XX_POLL | WM97XX_DELAY(delay)); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay (delay); + + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dbg ("adc sample timeout"); + return RC_PENUP; + } + + *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(adcsel); + + /* check we have correct sample */ + if ((*sample & WM97XX_ADCSEL_MASK) != adcsel) { + dbg ("adc wrong sample, read %x got %x", adcsel, + *sample & WM97XX_ADCSEL_MASK); + return RC_PENUP; + } + + if (!(*sample & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + + return RC_VALID; +} + +/* + * Sample the WM9705 touchscreen in polling mode + */ +static int wm9705_poll_touch(struct wm97xx* wm, struct wm97xx_data *data) +{ + int rc; + + if ((rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_X, &data->x)) != RC_VALID) + return rc; + if ((rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_Y, &data->y)) != RC_VALID) + return rc; + if (pil) { + if ((rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_PRES, &data->p)) != RC_VALID) + return rc; + } else + data->p = DEFAULT_PRESSURE; + + return RC_VALID; +} + +/* + * Enable WM9705 continuous mode, i.e. touch data is streamed across an AC97 slot + */ +static int wm9705_acc_enable (struct wm97xx* wm, int enable) +{ + u16 dig1, dig2; + int ret = 0; + + dig1 = wm->dig[1]; + dig2 = wm->dig[2]; + + if (enable) { + /* continous mode */ + if (wm->mach_ops->acc_startup && (ret = wm->mach_ops->acc_startup(wm)) < 0) + return ret; + dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK | + WM97XX_DELAY_MASK | WM97XX_SLT_MASK); + dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN | + WM97XX_DELAY (delay) | + WM97XX_SLT (wm->acc_slot) | + WM97XX_RATE (wm->acc_rate); + if (pil) + dig1 |= WM97XX_ADCSEL_PRES; + dig2 |= WM9705_PDEN; + } else { + dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN); + dig2 &= ~WM9705_PDEN; + if (wm->mach_ops->acc_shutdown) + wm->mach_ops->acc_shutdown(wm); + } + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); + return ret; +} + +struct wm97xx_codec_drv wm97xx_codec = { + .id = WM9705_ID2, + .name = "wm9705", + .poll_sample = wm9705_poll_sample, + .poll_touch = wm9705_poll_touch, + .acc_enable = wm9705_acc_enable, + .digitiser_ioctl = wm9705_digitiser_ioctl, +}; + +EXPORT_SYMBOL_GPL(wm97xx_codec); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("WM9705 Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/wm9712.c b/drivers/input/touchscreen/wm9712.c new file mode 100644 index 0000000..99433e9 --- /dev/null +++ b/drivers/input/touchscreen/wm9712.c @@ -0,0 +1,464 @@ +/* + * wm9712.c -- Codec driver for Wolfson WM9712 AC97 Codecs. + * + * Copyright 2003, 2004, 2005, 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 4th Jul 2005 Initial version. + * 6th Sep 2006 Mike Arthur <linux@wolfsonmicro.com> + * Added pre and post sample calls. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/wm97xx.h> + +#define TS_NAME "wm97xx" +#define WM9712_VERSION "0.61" +#define DEFAULT_PRESSURE 0xb0c0 + +/* + * Debug + */ +#if 0 +#define dbg(format, arg...) printk(KERN_DEBUG TS_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) +#endif +#define err(format, arg...) printk(KERN_ERR TS_NAME ": " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO TS_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING TS_NAME ": " format "\n" , ## arg) + +/* + * Module parameters + */ + +/* + * Set internal pull up for pen detect. + * + * Pull up is in the range 1.02k (least sensitive) to 64k (most sensitive) + * i.e. pull up resistance = 64k Ohms / rpu. + * + * Adjust this value if you are having problems with pen detect not + * detecting any down event. + */ +static int rpu = 8; +module_param(rpu, int, 0); +MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect."); + +/* + * Set current used for pressure measurement. + * + * Set pil = 2 to use 400uA + * pil = 1 to use 200uA and + * pil = 0 to disable pressure measurement. + * + * This is used to increase the range of values returned by the adc + * when measureing touchpanel pressure. + */ +static int pil = 0; +module_param(pil, int, 0); +MODULE_PARM_DESC(pil, "Set current used for pressure measurement."); + +/* + * Set threshold for pressure measurement. + * + * Pen down pressure below threshold is ignored. + */ +static int pressure = DEFAULT_PRESSURE & 0xfff; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement."); + +/* + * Set adc sample delay. + * + * For accurate touchpanel measurements, some settling time may be + * required between the switch matrix applying a voltage across the + * touchpanel plate and the ADC sampling the signal. + * + * This delay can be set by setting delay = n, where n is the array + * position of the delay in the array delay_table below. + * Long delays > 1ms are supported for completeness, but are not + * recommended. + */ +static int delay = 3; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "Set adc sample delay."); + +/* + * Set five_wire = 1 to use a 5 wire touchscreen. + * + * NOTE: Five wire mode does not allow for readback of pressure. + */ +static int five_wire; +module_param(five_wire, int, 0); +MODULE_PARM_DESC(five_wire, "Set to '1' to use 5-wire touchscreen."); + +/* + * Set adc mask function. + * + * Sources of glitch noise, such as signals driving an LCD display, may feed + * through to the touch screen plates and affect measurement accuracy. In + * order to minimise this, a signal may be applied to the MASK pin to delay or + * synchronise the sampling. + * + * 0 = No delay or sync + * 1 = High on pin stops conversions + * 2 = Edge triggered, edge on pin delays conversion by delay param (above) + * 3 = Edge triggered, edge on pin starts conversion after delay param + */ +static int mask = 0; +module_param(mask, int, 0); +MODULE_PARM_DESC(mask, "Set adc mask function."); + +/* + * Coordinate Polling Enable. + * + * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together + * for every poll. + */ +static int coord = 0; +module_param(coord, int, 0); +MODULE_PARM_DESC(coord, "Polling coordinate mode"); + +/* + * ADC sample delay times in uS + */ +static const int delay_table[] = { + 21, // 1 AC97 Link frames + 42, // 2 + 84, // 4 + 167, // 8 + 333, // 16 + 667, // 32 + 1000, // 48 + 1333, // 64 + 2000, // 96 + 2667, // 128 + 3333, // 160 + 4000, // 192 + 4667, // 224 + 5333, // 256 + 6000, // 288 + 0 // No delay, switch matrix always on +}; + +/* + * Delay after issuing a POLL command. + * + * The delay is 3 AC97 link frames + the touchpanel settling delay + */ +static inline void poll_delay(int d) +{ + udelay (3 * AC97_LINK_FRAME + delay_table [d]); +} + +/* + * set up the physical settings of the WM9712 + */ +static void init_wm9712_phy(struct wm97xx* wm) +{ + u16 dig1 = 0; + u16 dig2 = WM97XX_RPR | WM9712_RPU(1); + + /* WM9712 rpu */ + if (rpu) { + dig2 &= 0xffc0; + dig2 |= WM9712_RPU(rpu); + dbg("setting pen detect pull-up to %d Ohms",64000 / rpu); + } + + /* touchpanel pressure current*/ + if (pil == 2) { + dig2 |= WM9712_PIL; + dbg("setting pressure measurement current to 400uA."); + } else if (pil) + dbg("setting pressure measurement current to 200uA."); + if(!pil) + pressure = 0; + + /* WM9712 five wire */ + if (five_wire) { + dig2 |= WM9712_45W; + dbg("setting 5-wire touchscreen mode."); + } + + /* polling mode sample settling delay */ + if (delay < 0 || delay > 15) { + dbg("supplied delay out of range."); + delay = 4; + } + dig1 &= 0xff0f; + dig1 |= WM97XX_DELAY(delay); + dbg("setting adc sample delay to %d u Secs.", delay_table[delay]); + + /* mask */ + dig2 |= ((mask & 0x3) << 6); + if (mask) { + u16 reg; + /* Set GPIO4 as Mask Pin*/ + reg = wm97xx_reg_read(wm, AC97_MISC_AFE); + wm97xx_reg_write(wm, AC97_MISC_AFE, reg | WM97XX_GPIO_4); + reg = wm97xx_reg_read(wm, AC97_GPIO_CFG); + wm97xx_reg_write(wm, AC97_GPIO_CFG, reg | WM97XX_GPIO_4); + } + + /* wait - coord mode */ + if(coord) + dig2 |= WM9712_WAIT; + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); +} + +static int wm9712_digitiser_ioctl(struct wm97xx* wm, int cmd) +{ + u16 dig2 = wm->dig[2]; + + switch(cmd) { + case WM97XX_DIG_START: + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2 | WM97XX_PRP_DET_DIG); + wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */ + break; + case WM97XX_DIG_STOP: + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2 & ~WM97XX_PRP_DET_DIG); + break; + case WM97XX_AUX_PREPARE: + memcpy(wm->dig_save, wm->dig, sizeof(wm->dig)); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG); + break; + case WM97XX_DIG_RESTORE: + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]); + break; + case WM97XX_PHY_INIT: + init_wm9712_phy(wm); + break; + default: + return -EINVAL; + } + return 0; +} + +static inline int is_pden (struct wm97xx* wm) +{ + return wm->dig[2] & WM9712_PDEN; +} + +/* + * Read a sample from the WM9712 adc in polling mode. + */ +static int wm9712_poll_sample (struct wm97xx* wm, int adcsel, int *sample) +{ + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (adcsel & 0x8000) + adcsel = ((adcsel & 0x7fff) + 3) << 12; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(adcsel); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, adcsel | WM97XX_POLL | WM97XX_DELAY(delay)); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay (delay); + + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dbg ("adc sample timeout"); + return RC_PENUP; + } + + *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(adcsel); + + /* check we have correct sample */ + if ((*sample & WM97XX_ADCSEL_MASK) != adcsel) { + dbg ("adc wrong sample, read %x got %x", adcsel, + *sample & WM97XX_ADCSEL_MASK); + return RC_PENUP; + } + + if (!(*sample & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + + return RC_VALID; +} + +/* + * Read a coord from the WM9712 adc in polling mode. + */ +static int wm9712_poll_coord (struct wm97xx* wm, struct wm97xx_data *data) +{ + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, + WM97XX_COO | WM97XX_POLL | WM97XX_DELAY(delay)); + + /* wait 3 AC97 time slots + delay for conversion and read x */ + poll_delay(delay); + data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dbg ("adc sample timeout"); + return RC_PENUP; + } + + /* read back y data */ + data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (pil) + data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + else + data->p = DEFAULT_PRESSURE; + + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + + /* check we have correct sample */ + if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y)) + goto err; + if(pil && !(data->p & WM97XX_ADCSEL_PRES)) + goto err; + + if (!(data->x & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + return RC_VALID; +err: + return RC_PENUP; +} + +/* + * Sample the WM9712 touchscreen in polling mode + */ +static int wm9712_poll_touch(struct wm97xx* wm, struct wm97xx_data *data) +{ + int rc; + + if(coord) { + if((rc = wm9712_poll_coord(wm, data)) != RC_VALID) + return rc; + } else { + if ((rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_X, &data->x)) != RC_VALID) + return rc; + + if ((rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_Y, &data->y)) != RC_VALID) + return rc; + + if (pil && !five_wire) { + if ((rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_PRES, &data->p)) != RC_VALID) + return rc; + } else + data->p = DEFAULT_PRESSURE; + } + return RC_VALID; +} + +/* + * Enable WM9712 continuous mode, i.e. touch data is streamed across an AC97 slot + */ +static int wm9712_acc_enable (struct wm97xx* wm, int enable) +{ + u16 dig1, dig2; + int ret = 0; + + dig1 = wm->dig[1]; + dig2 = wm->dig[2]; + + if (enable) { + /* continous mode */ + if (wm->mach_ops->acc_startup && (ret = wm->mach_ops->acc_startup(wm)) < 0) + return ret; + dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK | + WM97XX_DELAY_MASK | WM97XX_SLT_MASK); + dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN | + WM97XX_DELAY (delay) | + WM97XX_SLT (wm->acc_slot) | + WM97XX_RATE (wm->acc_rate); + if (pil) + dig1 |= WM97XX_ADCSEL_PRES; + dig2 |= WM9712_PDEN; + } else { + dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN); + dig2 &= ~WM9712_PDEN; + if (wm->mach_ops->acc_shutdown) + wm->mach_ops->acc_shutdown(wm); + } + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); + return 0; +} + +struct wm97xx_codec_drv wm97xx_codec = { + .id = WM9712_ID2, + .name = "wm9712", + .poll_sample = wm9712_poll_sample, + .poll_touch = wm9712_poll_touch, + .acc_enable = wm9712_acc_enable, + .digitiser_ioctl = wm9712_digitiser_ioctl, +}; + +EXPORT_SYMBOL_GPL(wm97xx_codec); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("WM9712 Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/wm9713.c b/drivers/input/touchscreen/wm9713.c new file mode 100644 index 0000000..42f5f30 --- /dev/null +++ b/drivers/input/touchscreen/wm9713.c @@ -0,0 +1,461 @@ +/* + * wm9713.c -- Codec touch driver for Wolfson WM9713 AC97 Codec. + * + * Copyright 2003, 2004, 2005, 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 6th Sep 2006 Mike Arthur <linux@wolfsonmicro.com> + * Added pre and post sample calls. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/wm97xx.h> + +#define TS_NAME "wm97xx" +#define WM9713_VERSION "0.53" +#define DEFAULT_PRESSURE 0xb0c0 + +/* + * Debug + */ +#if 0 +#define dbg(format, arg...) printk(KERN_DEBUG TS_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) +#endif +#define err(format, arg...) printk(KERN_ERR TS_NAME ": " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO TS_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING TS_NAME ": " format "\n" , ## arg) + +/* + * Module parameters + */ + +/* + * Set internal pull up for pen detect. + * + * Pull up is in the range 1.02k (least sensitive) to 64k (most sensitive) + * i.e. pull up resistance = 64k Ohms / rpu. + * + * Adjust this value if you are having problems with pen detect not + * detecting any down event. + */ +static int rpu = 8; +module_param(rpu, int, 0); +MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect."); + +/* + * Set current used for pressure measurement. + * + * Set pil = 2 to use 400uA + * pil = 1 to use 200uA and + * pil = 0 to disable pressure measurement. + * + * This is used to increase the range of values returned by the adc + * when measureing touchpanel pressure. + */ +static int pil = 0; +module_param(pil, int, 0); +MODULE_PARM_DESC(pil, "Set current used for pressure measurement."); + +/* + * Set threshold for pressure measurement. + * + * Pen down pressure below threshold is ignored. + */ +static int pressure = DEFAULT_PRESSURE & 0xfff; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement."); + +/* + * Set adc sample delay. + * + * For accurate touchpanel measurements, some settling time may be + * required between the switch matrix applying a voltage across the + * touchpanel plate and the ADC sampling the signal. + * + * This delay can be set by setting delay = n, where n is the array + * position of the delay in the array delay_table below. + * Long delays > 1ms are supported for completeness, but are not + * recommended. + */ +static int delay = 4; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "Set adc sample delay."); + +/* + * Set adc mask function. + * + * Sources of glitch noise, such as signals driving an LCD display, may feed + * through to the touch screen plates and affect measurement accuracy. In + * order to minimise this, a signal may be applied to the MASK pin to delay or + * synchronise the sampling. + * + * 0 = No delay or sync + * 1 = High on pin stops conversions + * 2 = Edge triggered, edge on pin delays conversion by delay param (above) + * 3 = Edge triggered, edge on pin starts conversion after delay param + */ +static int mask = 0; +module_param(mask, int, 0); +MODULE_PARM_DESC(mask, "Set adc mask function."); + +/* + * Coordinate Polling Enable. + * + * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together + * for every poll. + */ +static int coord = 0; +module_param(coord, int, 0); +MODULE_PARM_DESC(coord, "Polling coordinate mode"); + +/* + * ADC sample delay times in uS + */ +static const int delay_table[] = { + 21, // 1 AC97 Link frames + 42, // 2 + 84, // 4 + 167, // 8 + 333, // 16 + 667, // 32 + 1000, // 48 + 1333, // 64 + 2000, // 96 + 2667, // 128 + 3333, // 160 + 4000, // 192 + 4667, // 224 + 5333, // 256 + 6000, // 288 + 0 // No delay, switch matrix always on +}; + +/* + * Delay after issuing a POLL command. + * + * The delay is 3 AC97 link frames + the touchpanel settling delay + */ +static inline void poll_delay(int d) +{ + udelay (3 * AC97_LINK_FRAME + delay_table [d]); +} + +/* + * set up the physical settings of the WM9713 + */ +static void init_wm9713_phy(struct wm97xx* wm) +{ + u16 dig1 = 0, dig2, dig3; + + /* default values */ + dig2 = WM97XX_DELAY(4) | WM97XX_SLT(5); + dig3= WM9712_RPU(1); + + /* rpu */ + if (rpu) { + dig3 &= 0xffc0; + dig3 |= WM9712_RPU(rpu); + info("setting pen detect pull-up to %d Ohms",64000 / rpu); + } + + /* touchpanel pressure */ + if (pil == 2) { + dig3 |= WM9712_PIL; + info("setting pressure measurement current to 400uA."); + } else if (pil) + info ("setting pressure measurement current to 200uA."); + if(!pil) + pressure = 0; + + /* sample settling delay */ + if (delay < 0 || delay > 15) { + info ("supplied delay out of range."); + delay = 4; + info("setting adc sample delay to %d u Secs.", delay_table[delay]); + } + dig2 &= 0xff0f; + dig2 |= WM97XX_DELAY(delay); + + /* mask */ + dig3 |= ((mask & 0x3) << 4); + if(coord) + dig3 |= WM9713_WAIT; + + wm->misc = wm97xx_reg_read(wm, 0x5a); + + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3); + wm97xx_reg_write(wm, AC97_GPIO_STICKY, 0x0); +} + +static int wm9713_digitiser_ioctl(struct wm97xx* wm, int cmd) +{ + u16 val = 0; + + switch(cmd){ + case WM97XX_DIG_START: + val = wm97xx_reg_read(wm, AC97_EXTENDED_MID); + wm97xx_reg_write(wm, AC97_EXTENDED_MID, val & 0x7fff); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] | WM97XX_PRP_DET_DIG); + wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */ + break; + case WM97XX_DIG_STOP: + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] & ~WM97XX_PRP_DET_DIG); + val = wm97xx_reg_read(wm, AC97_EXTENDED_MID); + wm97xx_reg_write(wm, AC97_EXTENDED_MID, val | 0x8000); + break; + case WM97XX_AUX_PREPARE: + memcpy(wm->dig_save, wm->dig, sizeof(wm->dig)); + wm97xx_reg_write(wm, AC97_WM9713_DIG1, 0); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, 0); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, WM97XX_PRP_DET_DIG); + break; + case WM97XX_DIG_RESTORE: + wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig_save[0]); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig_save[1]); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig_save[2]); + break; + case WM97XX_PHY_INIT: + init_wm9713_phy(wm); + break; + default: + return -EINVAL; + } + return 0; +} + +static inline int is_pden (struct wm97xx* wm) +{ + return wm->dig[2] & WM9713_PDEN; +} + +/* + * Read a sample from the WM9713 adc in polling mode. + */ +static int wm9713_poll_sample (struct wm97xx* wm, int adcsel, int *sample) +{ + u16 dig1; + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (adcsel & 0x8000) + adcsel = 1 << ((adcsel & 0x7fff) + 3); + + dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1); + dig1 &= ~WM9713_ADCSEL_MASK; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(adcsel); + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1 | adcsel |WM9713_POLL); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL) && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dbg ("adc sample timeout"); + return RC_PENUP; + } + + *sample =wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(adcsel); + + /* check we have correct sample */ + if ((*sample & WM97XX_ADCSRC_MASK) != ffs(adcsel >> 1) << 12) { + dbg ("adc wrong sample, read %x got %x", adcsel, + *sample & WM97XX_ADCSRC_MASK); + return RC_PENUP; + } + + if (!(*sample & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + + return RC_VALID; +} + +/* + * Read a coordinate from the WM9713 adc in polling mode. + */ +static int wm9713_poll_coord (struct wm97xx* wm, struct wm97xx_data *data) +{ + u16 dig1; + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1); + dig1 &= ~WM9713_ADCSEL_MASK; + if(pil) + dig1 |= WM97XX_ADCSEL_PRES; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1 | WM9713_POLL | WM9713_COO); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL) && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dbg ("adc sample timeout"); + return RC_PENUP; + } + + /* read back data */ + data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (pil) + data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + else + data->p = DEFAULT_PRESSURE; + + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + + /* check we have correct sample */ + if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y)) + goto err; + if(pil && !(data->p & WM97XX_ADCSEL_PRES)) + goto err; + + if (!(data->x & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + return RC_VALID; +err: + return RC_PENUP; +} + +/* + * Sample the WM9713 touchscreen in polling mode + */ +static int wm9713_poll_touch(struct wm97xx* wm, struct wm97xx_data *data) +{ + int rc; + + if(coord) { + if((rc = wm9713_poll_coord(wm, data)) != RC_VALID) + return rc; + } else { + if ((rc = wm9713_poll_sample(wm, WM9713_ADCSEL_X, &data->x)) != RC_VALID) + return rc; + if ((rc = wm9713_poll_sample(wm, WM9713_ADCSEL_Y, &data->y)) != RC_VALID) + return rc; + if (pil) { + if ((rc = wm9713_poll_sample(wm, WM9713_ADCSEL_PRES, &data->p)) != RC_VALID) + return rc; + } else + data->p = DEFAULT_PRESSURE; + } + return RC_VALID; +} + +/* + * Enable WM9713 continuous mode, i.e. touch data is streamed across an AC97 slot + */ +static int wm9713_acc_enable (struct wm97xx* wm, int enable) +{ + u16 dig1, dig2, dig3; + int ret = 0; + + dig1 = wm->dig[0]; + dig2 = wm->dig[1]; + dig3 = wm->dig[2]; + + if (enable) { + /* continous mode */ + if (wm->mach_ops->acc_startup && + (ret = wm->mach_ops->acc_startup(wm)) < 0) + return ret; + + dig1 &= ~WM9713_ADCSEL_MASK; + dig1 |= WM9713_CTC | WM9713_COO | WM9713_ADCSEL_X | WM9713_ADCSEL_Y; + if (pil) + dig1 |= WM9713_ADCSEL_PRES; + dig2 &= ~(WM97XX_DELAY_MASK | WM97XX_SLT_MASK | WM97XX_CM_RATE_MASK); + dig2 |= WM97XX_SLEN | WM97XX_DELAY (delay) | + WM97XX_SLT (wm->acc_slot) | WM97XX_RATE (wm->acc_rate); + dig3 |= WM9713_PDEN; + } else { + dig1 &= ~(WM9713_CTC | WM9713_COO); + dig2 &= ~WM97XX_SLEN; + dig3 &= ~WM9713_PDEN; + if (wm->mach_ops->acc_shutdown) + wm->mach_ops->acc_shutdown(wm); + } + + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3); + return ret; +} + +struct wm97xx_codec_drv wm97xx_codec = { + .id = WM9713_ID2, + .name = "wm9713", + .poll_sample = wm9713_poll_sample, + .poll_touch = wm9713_poll_touch, + .acc_enable = wm9713_acc_enable, + .digitiser_ioctl = wm9713_digitiser_ioctl, +}; + +EXPORT_SYMBOL_GPL(wm97xx_codec); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("WM9713 Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/wm97xx-core.c b/drivers/input/touchscreen/wm97xx-core.c new file mode 100644 index 0000000..87179d2 --- /dev/null +++ b/drivers/input/touchscreen/wm97xx-core.c @@ -0,0 +1,859 @@ +/* + * wm97xx-core.c -- Touch screen driver core for Wolfson WM9705, WM9712 + * and WM9713 AC97 Codecs. + * + * Copyright 2003, 2004, 2005, 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Notes: + * + * Features: + * - supports WM9705, WM9712, WM9713 + * - polling mode + * - continuous mode (arch-dependent) + * - adjustable rpu/dpp settings + * - adjustable pressure current + * - adjustable sample settle delay + * - 4 and 5 wire touchscreens (5 wire is WM9712 only) + * - pen down detection + * - battery monitor + * - sample AUX adc's + * - power management + * - codec GPIO + * - codec event notification + * Todo + * - Support for async sampling control for noisy LCD's. + * + * Revision history + * 7th May 2003 Initial version. + * 6th June 2003 Added non module support and AC97 registration. + * 18th June 2003 Added AUX adc sampling. + * 23rd June 2003 Did some minimal reformatting, fixed a couple of + * codec_mutexing bugs and noted a race to fix. + * 24th June 2003 Added power management and fixed race condition. + * 10th July 2003 Changed to a misc device. + * 31st July 2003 Moved TS_EVENT and TS_CAL to wm97xx.h + * 8th Aug 2003 Added option for read() calling wm97xx_sample_touch() + * because some ac97_read/ac_97_write call schedule() + * 7th Nov 2003 Added Input touch event interface, stanley.cai@intel.com + * 13th Nov 2003 Removed h3600 touch interface, added interrupt based + * pen down notification and implemented continous mode + * on XScale arch. + * 16th Nov 2003 Ian Molton <spyro@f2s.com> + * Modified so that it suits the new 2.6 driver model. + * 25th Jan 2004 Andrew Zabolotny <zap@homelink.ru> + * Implemented IRQ-driven pen down detection, implemented + * the private API meant to be exposed to platform-specific + * drivers, reorganized the driver so that it supports + * an arbitrary number of devices. + * 1st Feb 2004 Moved continuous mode handling to a separate + * architecture-dependent file. For now only PXA + * built-in AC97 controller is supported (pxa-ac97-wm97xx.c). + * 11th Feb 2004 Reduced CPU usage by keeping a cached copy of both + * digitizer registers instead of reading them every time. + * A reorganization of the whole code for better + * error handling. + * 17th Apr 2004 Added BMON support. + * 17th Nov 2004 Added codec GPIO, codec event handling (real and virtual + * GPIOs) and 2.6 power management. + * 29th Nov 2004 Added WM9713 support. + * 4th Jul 2005 Moved codec specific code out to seperate files. + * 6th Sep 2006 Mike Arthur <linux@wolfsonmicro.com> + * Added bus interface. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/proc_fs.h> +#include <linux/pm.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> +#include <linux/workqueue.h> +#include <linux/device.h> +#include <linux/freezer.h> +#include <linux/wm97xx.h> +#include <asm/uaccess.h> +#include <asm/io.h> + +#include <asm/arch/irqs.h> + +#define TS_NAME "wm97xx" +#define WM_CORE_VERSION "0.64" +#define DEFAULT_PRESSURE 0xb0c0 + + +/* + * Touchscreen absolute values + * + * These parameters are used to help the input layer discard out of + * range readings and reduce jitter etc. + * + * o min, max:- indicate the min and max values your touch screen returns + * o fuzz:- use a higher number to reduce jitter + * + * The default values correspond to Mainstone II in QVGA mode + * + * Please read + * Documentation/input/input-programming.txt for more details. + */ + +static int abs_x[3] = {350,3900,5}; +module_param_array(abs_x, int, NULL, 0); +MODULE_PARM_DESC(abs_x, "Touchscreen absolute X min, max, fuzz"); + +static int abs_y[3] = {320,3750,40}; +module_param_array(abs_y, int, NULL, 0); +MODULE_PARM_DESC(abs_y, "Touchscreen absolute Y min, max, fuzz"); + +static int abs_p[3] = {0,150,4}; +module_param_array(abs_p, int, NULL, 0); +MODULE_PARM_DESC(abs_p, "Touchscreen absolute Pressure min, max, fuzz"); + +/* + * Debug + */ +#if 0 +#define dbg(format, arg...) printk(KERN_DEBUG TS_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) +#endif +#define err(format, arg...) printk(KERN_ERR TS_NAME ": " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO TS_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING TS_NAME ": " format "\n" , ## arg) + +/* + * wm97xx IO access, all IO locking done by AC97 layer + */ +int wm97xx_reg_read(struct wm97xx *wm, u16 reg) +{ + if (wm->ac97) + return wm->ac97->bus->ops->read(wm->ac97, reg); + else + return -1; +} +EXPORT_SYMBOL_GPL(wm97xx_reg_read); + +void wm97xx_reg_write(struct wm97xx *wm, u16 reg, u16 val) +{ + /* cache digitiser registers */ + if(reg >= AC97_WM9713_DIG1 && reg <= AC97_WM9713_DIG3) + wm->dig[(reg - AC97_WM9713_DIG1) >> 1] = val; + + /* cache gpio regs */ + if(reg >= AC97_GPIO_CFG && reg <= AC97_MISC_AFE) + wm->gpio[(reg - AC97_GPIO_CFG) >> 1] = val; + + /* wm9713 irq reg */ + if(reg == 0x5a) + wm->misc = val; + + if (wm->ac97) + wm->ac97->bus->ops->write(wm->ac97, reg, val); +} +EXPORT_SYMBOL_GPL(wm97xx_reg_write); + +/** + * wm97xx_read_aux_adc - Read the aux adc. + * @wm: wm97xx device. + * @adcsel: codec ADC to be read + * + * Reads the selected AUX ADC. + */ + +int wm97xx_read_aux_adc(struct wm97xx *wm, u16 adcsel) +{ + int power_adc = 0, auxval; + u16 power = 0; + + /* get codec */ + mutex_lock(&wm->codec_mutex); + + /* When the touchscreen is not in use, we may have to power up the AUX ADC + * before we can use sample the AUX inputs-> + */ + if (wm->id == WM9713_ID2 && + (power = wm97xx_reg_read(wm, AC97_EXTENDED_MID)) & 0x8000) { + power_adc = 1; + wm97xx_reg_write(wm, AC97_EXTENDED_MID, power & 0x7fff); + } + + /* Prepare the codec for AUX reading */ + wm->codec->digitiser_ioctl(wm, WM97XX_AUX_PREPARE); + + /* Turn polling mode on to read AUX ADC */ + wm->pen_probably_down = 1; + wm->codec->poll_sample(wm, adcsel, &auxval); + + if (power_adc) + wm97xx_reg_write(wm, AC97_EXTENDED_MID, power | 0x8000); + + wm->codec->digitiser_ioctl(wm, WM97XX_DIG_RESTORE); + + wm->pen_probably_down = 0; + + mutex_unlock(&wm->codec_mutex); + return auxval & 0xfff; +} +EXPORT_SYMBOL_GPL(wm97xx_read_aux_adc); + +/** + * wm97xx_get_gpio - Get the status of a codec GPIO. + * @wm: wm97xx device. + * @gpio: gpio + * + * Get the status of a codec GPIO pin + */ + +wm97xx_gpio_status_t wm97xx_get_gpio(struct wm97xx *wm, u32 gpio) +{ + u16 status; + wm97xx_gpio_status_t ret; + + mutex_lock(&wm->codec_mutex); + status = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + + if (status & gpio) + ret = WM97XX_GPIO_HIGH; + else + ret = WM97XX_GPIO_LOW; + + mutex_unlock(&wm->codec_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm97xx_get_gpio); + +/** + * wm97xx_set_gpio - Set the status of a codec GPIO. + * @wm: wm97xx device. + * @gpio: gpio + * + * + * Set the status of a codec GPIO pin + */ + +void wm97xx_set_gpio(struct wm97xx *wm, u32 gpio, + wm97xx_gpio_status_t status) +{ + u16 reg; + + mutex_lock(&wm->codec_mutex); + reg = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + + if (status & WM97XX_GPIO_HIGH) + reg |= gpio; + else + reg &= ~gpio; + + if (wm->id == WM9712_ID2) + wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg << 1); + else + wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg); + mutex_unlock(&wm->codec_mutex); +} +EXPORT_SYMBOL_GPL(wm97xx_set_gpio); + +/* + * Codec GPIO pin configuration, this set's pin direction, polarity, + * stickyness and wake up. + */ +void wm97xx_config_gpio(struct wm97xx *wm, u32 gpio, wm97xx_gpio_dir_t dir, + wm97xx_gpio_pol_t pol, wm97xx_gpio_sticky_t sticky, + wm97xx_gpio_wake_t wake) +{ + u16 reg; + + mutex_lock(&wm->codec_mutex); + reg = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); + + if (pol == WM97XX_GPIO_POL_HIGH) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, reg); + reg = wm97xx_reg_read(wm, AC97_GPIO_STICKY); + + if (sticky == WM97XX_GPIO_STICKY) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_STICKY, reg); + reg = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP); + + if (wake == WM97XX_GPIO_WAKE) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, reg); + reg = wm97xx_reg_read(wm, AC97_GPIO_CFG); + + if (dir == WM97XX_GPIO_IN) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_CFG, reg); + mutex_unlock(&wm->codec_mutex); +} +EXPORT_SYMBOL_GPL(wm97xx_config_gpio); + +/* + * Handle a pen down interrupt. + */ +static void wm97xx_pen_irq_worker(struct work_struct *w) +{ +/* struct wm97xx *wm = (struct wm97xx *) ptr; */ + struct wm97xx *wm = container_of(w, struct wm97xx, pen_event_work); + + /* do we need to enable the touch panel reader */ + if (wm->id == WM9705_ID2) { + if (wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD) & WM97XX_PEN_DOWN) + wm->pen_is_down = 1; + else + wm->pen_is_down = 0; + wake_up_interruptible(&wm->pen_irq_wait); + } else { + u16 status, pol; + mutex_lock(&wm->codec_mutex); + status = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + pol = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); + + if (WM97XX_GPIO_13 & pol & status) { + wm->pen_is_down = 1; + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol & ~WM97XX_GPIO_13); + } else { + wm->pen_is_down = 0; + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol | WM97XX_GPIO_13); + } + + if (wm->id == WM9712_ID2) + wm97xx_reg_write(wm, AC97_GPIO_STATUS, (status & ~WM97XX_GPIO_13) << 1); + else + wm97xx_reg_write(wm, AC97_GPIO_STATUS, status & ~WM97XX_GPIO_13); + mutex_unlock(&wm->codec_mutex); + wake_up_interruptible(&wm->pen_irq_wait); + } + + if (!wm->pen_is_down && wm->mach_ops && wm->mach_ops->acc_enabled) + wm->mach_ops->acc_pen_up(wm); + enable_irq(wm->pen_irq); +} + +/* + * Codec PENDOWN irq handler + * + * We have to disable the codec interrupt in the handler because it can + * take upto 1ms to clear the interrupt source. The interrupt is then enabled + * again in the slow handler when the source has been cleared. + */ +static irqreturn_t wm97xx_pen_interrupt(int irq, void *dev_id) +{ + struct wm97xx *wm = (struct wm97xx *) dev_id; + disable_irq(wm->pen_irq); + queue_work(wm->pen_irq_workq, &wm->pen_event_work); + return IRQ_HANDLED; +} + +/* + * initialise pen IRQ handler and workqueue + */ +static int wm97xx_init_pen_irq(struct wm97xx *wm) +{ + u16 reg; + + INIT_WORK(&wm->pen_event_work, wm97xx_pen_irq_worker/* , wm */); + if ((wm->pen_irq_workq = + create_singlethread_workqueue("kwm97pen")) == NULL) { + err("could not create pen irq work queue"); + wm->pen_irq = 0; + return -EINVAL; + } + + + wm->pen_irq = IRQ_GPIO(90); + if (request_irq (wm->pen_irq, wm97xx_pen_interrupt, SA_SHIRQ, "wm97xx-pen", wm)) { + err("could not register codec pen down interrupt, will poll for pen down"); + destroy_workqueue(wm->pen_irq_workq); + wm->pen_irq = 0; + return -EINVAL; + } + + /* enable PEN down on wm9712/13 */ + if (wm->id != WM9705_ID2) { + reg = wm97xx_reg_read(wm, AC97_MISC_AFE); + wm97xx_reg_write(wm, AC97_MISC_AFE, reg & 0xfffb); + reg = wm97xx_reg_read(wm, 0x5a); + wm97xx_reg_write(wm, 0x5a, reg & ~0x0001); + } + + return 0; +} + +/* Private struct for communication between struct wm97xx_tshread + * and wm97xx_read_samples */ +struct ts_state { + int sleep_time; + int min_sleep_time; +}; + +static int wm97xx_read_samples(struct wm97xx *wm, struct ts_state *state) +{ + struct wm97xx_data data; + int rc; + + mutex_lock(&wm->codec_mutex); + + if (wm->mach_ops && wm->mach_ops->acc_enabled) + rc = wm->mach_ops->acc_pen_down(wm); + else + rc = wm->codec->poll_touch(wm, &data); + + if (rc & RC_PENUP) { + if (wm->pen_is_down) { + wm->pen_is_down = 0; + dbg("pen up"); + input_report_abs(wm->input_dev, ABS_PRESSURE, 0); + input_sync(wm->input_dev); + } else if (!(rc & RC_AGAIN)) { + /* We need high frequency updates only while pen is down, + * the user never will be able to touch screen faster than + * a few times per second... On the other hand, when the + * user is actively working with the touchscreen we don't + * want to lose the quick response. So we will slowly + * increase sleep time after the pen is up and quicky + * restore it to ~one task switch when pen is down again. + */ + if (state->sleep_time < HZ / 10) + state->sleep_time++; + } + + } else if (rc & RC_VALID) { + dbg("pen down: x=%x:%d, y=%x:%d, pressure=%x:%d\n", + data.x >> 12, data.x & 0xfff, data.y >> 12, + data.y & 0xfff, data.p >> 12, data.p & 0xfff); + input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff); + input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff); + input_report_abs(wm->input_dev, ABS_PRESSURE, data.p & 0xfff); + input_sync(wm->input_dev); + wm->pen_is_down = 1; + state->sleep_time = state->min_sleep_time; + } else if (rc & RC_PENDOWN) { + dbg("pen down"); + wm->pen_is_down = 1; + state->sleep_time = state->min_sleep_time; + } + + mutex_unlock(&wm->codec_mutex); + return rc; +} + +/* +* The touchscreen sample reader thread. +*/ +static int wm97xx_ts_read(void *data) +{ + int rc; + struct ts_state state; + struct wm97xx *wm = (struct wm97xx *) data; + + /* set up thread context */ + wm->ts_task = current; + daemonize("kwm97xxts"); + + if (wm->codec == NULL) { + wm->ts_task = NULL; + printk(KERN_ERR "codec is NULL, bailing\n"); + } + + complete(&wm->ts_init); + wm->pen_is_down = 0; + state.min_sleep_time = HZ >= 100 ? HZ / 100 : 1; + if (state.min_sleep_time < 1) + state.min_sleep_time = 1; + state.sleep_time = state.min_sleep_time; + + /* touch reader loop */ + while (wm->ts_task) { + do { + try_to_freeze(); + rc = wm97xx_read_samples(wm, &state); + } while (rc & RC_AGAIN); + if (!wm->pen_is_down && wm->pen_irq) { + /* Nice, we don't have to poll for pen down event */ + wait_event_interruptible(wm->pen_irq_wait, wm->pen_is_down); + } else { + set_task_state(current, TASK_INTERRUPTIBLE); + schedule_timeout(state.sleep_time); + } + } + complete_and_exit(&wm->ts_exit, 0); +} + +/** + * wm97xx_ts_input_open - Open the touch screen input device. + * @idev: Input device to be opened. + * + * Called by the input sub system to open a wm97xx touchscreen device. + * Starts the touchscreen thread and touch digitiser. + */ +static int wm97xx_ts_input_open(struct input_dev *idev) +{ + int ret = 0; + struct wm97xx *wm = (struct wm97xx *) idev->private; + + mutex_lock(&wm->codec_mutex); + /* first time opened ? */ + if (wm->ts_use_count++ == 0) { + /* start touchscreen thread */ + init_completion(&wm->ts_init); + init_completion(&wm->ts_exit); + ret = kernel_thread(wm97xx_ts_read, wm, CLONE_KERNEL); + + if (ret >= 0) { + wait_for_completion(&wm->ts_init); + if (wm->ts_task == NULL) + ret = -EINVAL; + } else { + mutex_unlock(&wm->codec_mutex); + return ret; + } + + /* start digitiser */ + if (wm->mach_ops && wm->mach_ops->acc_enabled) + wm->codec->acc_enable(wm, 1); + wm->codec->digitiser_ioctl(wm, WM97XX_DIG_START); + + /* init pen down/up irq handling */ + if (wm->pen_irq) { + wm97xx_init_pen_irq(wm); + + if (wm->pen_irq == 0) { + /* we failed to get an irq for pen down events, + * so we resort to polling. kickstart the reader */ + wm->pen_is_down = 1; + wake_up_interruptible(&wm->pen_irq_wait); + } + } + } + + mutex_unlock(&wm->codec_mutex); + return 0; +} + +/** + * wm97xx_ts_input_close - Close the touch screen input device. + * @idev: Input device to be closed. + * + * Called by the input sub system to close a wm97xx touchscreen device. + * Kills the touchscreen thread and stops the touch digitiser. + */ + +static void wm97xx_ts_input_close(struct input_dev *idev) +{ + struct wm97xx *wm = (struct wm97xx *) idev->private; + + mutex_lock(&wm->codec_mutex); + if (--wm->ts_use_count == 0) { + /* destroy workqueues and free irqs */ + if (wm->pen_irq) { + free_irq(wm->pen_irq, wm); + destroy_workqueue(wm->pen_irq_workq); + } + + /* kill thread */ + if (wm->ts_task) { + wm->ts_task = NULL; + wm->pen_is_down = 1; + mutex_unlock(&wm->codec_mutex); + wake_up_interruptible(&wm->pen_irq_wait); + wait_for_completion(&wm->ts_exit); + wm->pen_is_down = 0; + mutex_lock(&wm->codec_mutex); + } + + /* stop digitiser */ + wm->codec->digitiser_ioctl(wm, WM97XX_DIG_STOP); + if (wm->mach_ops && wm->mach_ops->acc_enabled) + wm->codec->acc_enable(wm, 0); + } + mutex_unlock(&wm->codec_mutex); +} + +/* + * Bus interface to allow for client drivers codec access + * e.g. battery monitor + */ +static int wm97xx_bus_match(struct device *dev, struct device_driver *drv) +{ + return !(strcmp(dev->bus_id,drv->name)); +} + +/* + * The AC97 audio driver will do all the Codec suspend and resume + * tasks. This is just for anything machine specific or extra. + */ +static int wm97xx_bus_suspend(struct device *dev, pm_message_t state) +{ + int ret = 0; + + if (dev->driver && dev->driver->suspend) + ret = dev->driver->suspend(dev, state); + + return ret; +} + +static int wm97xx_bus_resume(struct device *dev) +{ + int ret = 0; + + if (dev->driver && dev->driver->resume) + ret = dev->driver->resume(dev); + + return ret; +} + +struct bus_type wm97xx_bus_type = { + .name = "wm97xx", + .match = wm97xx_bus_match, + .suspend = wm97xx_bus_suspend, + .resume = wm97xx_bus_resume, +}; +EXPORT_SYMBOL_GPL(wm97xx_bus_type); + +static void wm97xx_release(struct device *dev) +{ + kfree(dev); +} + +static int wm97xx_probe(struct device *dev) +{ + struct wm97xx* wm; + int ret = 0, id = 0; + + if (!(wm = kzalloc(sizeof(struct wm97xx), GFP_KERNEL))) + return -ENOMEM; + mutex_init(&wm->codec_mutex); + + init_waitqueue_head(&wm->pen_irq_wait); + wm->dev = dev; + dev->driver_data = wm; + wm->ac97 = to_ac97_t(dev); + + /* check that we have a supported codec */ + if ((id = wm97xx_reg_read(wm, AC97_VENDOR_ID1)) != WM97XX_ID1) { + err("could not find a wm97xx, found a %x instead\n", id); + kfree(wm); + return -ENODEV; + } + + wm->id = wm97xx_reg_read(wm, AC97_VENDOR_ID2); + if(wm->id != wm97xx_codec.id) { + err("could not find a the selected codec, please build for wm97%2x", wm->id & 0xff); + kfree(wm); + return -ENODEV; + } + + if((wm->input_dev = input_allocate_device()) == NULL) { + kfree(wm); + return -ENOMEM; + } + + /* set up touch configuration */ + info("detected a wm97%2x codec", wm->id & 0xff); + wm->input_dev->name = "wm97xx touchscreen"; + wm->input_dev->open = wm97xx_ts_input_open; + wm->input_dev->close = wm97xx_ts_input_close; + set_bit(EV_ABS, wm->input_dev->evbit); + set_bit(ABS_X, wm->input_dev->absbit); + set_bit(ABS_Y, wm->input_dev->absbit); + set_bit(ABS_PRESSURE, wm->input_dev->absbit); + wm->input_dev->absmax[ABS_X] = abs_x[1]; + wm->input_dev->absmax[ABS_Y] = abs_y[1]; + wm->input_dev->absmax[ABS_PRESSURE] = abs_p[1]; + wm->input_dev->absmin[ABS_X] = abs_x[0]; + wm->input_dev->absmin[ABS_Y] = abs_y[0]; + wm->input_dev->absmin[ABS_PRESSURE] = abs_p[0]; + wm->input_dev->absfuzz[ABS_X] = abs_x[2]; + wm->input_dev->absfuzz[ABS_Y] = abs_y[2]; + wm->input_dev->absfuzz[ABS_PRESSURE] = abs_p[2]; + wm->input_dev->private = wm; + wm->codec = &wm97xx_codec; + if((ret = input_register_device(wm->input_dev)) < 0) { + kfree(wm); + return -ENOMEM; + } + + /* set up physical characteristics */ + wm->codec->digitiser_ioctl(wm, WM97XX_PHY_INIT); + + /* load gpio cache */ + wm->gpio[0] = wm97xx_reg_read(wm, AC97_GPIO_CFG); + wm->gpio[1] = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); + wm->gpio[2] = wm97xx_reg_read(wm, AC97_GPIO_STICKY); + wm->gpio[3] = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP); + wm->gpio[4] = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + wm->gpio[5] = wm97xx_reg_read(wm, AC97_MISC_AFE); + + /* register our battery device */ + if (!(wm->battery_dev = kzalloc(sizeof(struct device), GFP_KERNEL))) { + ret = -ENOMEM; + goto batt_err; + } + wm->battery_dev->bus = &wm97xx_bus_type; + strcpy(wm->battery_dev->bus_id,"wm97xx-battery"); + wm->battery_dev->driver_data = wm; + wm->battery_dev->parent = dev; + wm->battery_dev->release = wm97xx_release; + if((ret = device_register(wm->battery_dev)) < 0) + goto batt_reg_err; + + /* register our extended touch device (for machine specific extensions) */ + if (!(wm->touch_dev = kzalloc(sizeof(struct device), GFP_KERNEL))) { + ret = -ENOMEM; + goto touch_err; + } + wm->touch_dev->bus = &wm97xx_bus_type; + strcpy(wm->touch_dev->bus_id,"wm97xx-touchscreen"); + wm->touch_dev->driver_data = wm; + wm->touch_dev->parent = dev; + wm->touch_dev->release = wm97xx_release; + if((ret = device_register(wm->touch_dev)) < 0) + goto touch_reg_err; + + return ret; + +touch_reg_err: + kfree(wm->touch_dev); +touch_err: + device_unregister(wm->battery_dev); +batt_reg_err: + kfree(wm->battery_dev); +batt_err: + input_unregister_device(wm->input_dev); + kfree(wm); + return ret; +} + +static int wm97xx_remove(struct device *dev) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + + /* Stop touch reader thread */ + if (wm->ts_task) { + wm->ts_task = NULL; + wm->pen_is_down = 1; + wake_up_interruptible(&wm->pen_irq_wait); + wait_for_completion(&wm->ts_exit); + } + device_unregister(wm->battery_dev); + device_unregister(wm->touch_dev); + input_unregister_device(wm->input_dev); + + kfree(wm); + return 0; +} + +#ifdef CONFIG_PM +int wm97xx_resume(struct device* dev) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + + /* restore digitiser and gpio's */ + if(wm->id == WM9713_ID2) { + wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig[0]); + wm97xx_reg_write(wm, 0x5a, wm->misc); + if(wm->ts_use_count) { + u16 reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) & 0x7fff; + wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg); + } + } + + wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig[1]); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2]); + + wm97xx_reg_write(wm, AC97_GPIO_CFG, wm->gpio[0]); + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, wm->gpio[1]); + wm97xx_reg_write(wm, AC97_GPIO_STICKY, wm->gpio[2]); + wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, wm->gpio[3]); + wm97xx_reg_write(wm, AC97_GPIO_STATUS, wm->gpio[4]); + wm97xx_reg_write(wm, AC97_MISC_AFE, wm->gpio[5]); + + return 0; +} + +#else +#define wm97xx_resume NULL +#endif + +/* + * Machine specific operations + */ +int wm97xx_register_mach_ops(struct wm97xx *wm, struct wm97xx_mach_ops *mach_ops) +{ + mutex_lock(&wm->codec_mutex); + if(wm->mach_ops) { + mutex_unlock(&wm->codec_mutex); + return -EINVAL; + } + wm->mach_ops = mach_ops; + mutex_unlock(&wm->codec_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(wm97xx_register_mach_ops); + +void wm97xx_unregister_mach_ops(struct wm97xx *wm) +{ + mutex_lock(&wm->codec_mutex); + wm->mach_ops = NULL; + mutex_unlock(&wm->codec_mutex); +} +EXPORT_SYMBOL_GPL(wm97xx_unregister_mach_ops); + +static struct device_driver wm97xx_driver = { + .name = "ac97", + .bus = &ac97_bus_type, + .owner = THIS_MODULE, + .probe = wm97xx_probe, + .remove = wm97xx_remove, + .resume = wm97xx_resume, +}; + +static int __init wm97xx_init(void) +{ + int ret; + + info("version %s liam.girdwood@wolfsonmicro.com", WM_CORE_VERSION); + if((ret = bus_register(&wm97xx_bus_type)) < 0) + return ret; + return driver_register(&wm97xx_driver); +} + +static void __exit wm97xx_exit(void) +{ + driver_unregister(&wm97xx_driver); + bus_unregister(&wm97xx_bus_type); +} + +module_init(wm97xx_init); +module_exit(wm97xx_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("WM97xx Core - Touch Screen / AUX ADC / GPIO Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 4468cb3..01b4828 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -101,6 +101,12 @@ config LEDS_GPIO outputs. To be useful the particular board must have LEDs and they must be connected to the GPIO lines. +config LEDS_EM_X270 + tristate "LED Support for the CompuLab EM-X270" + depends on LEDS_CLASS && MACH_EM_X270 + help + This option enables support for the LEDs on CompuLab EM-X270. + comment "LED Triggers" config LEDS_TRIGGERS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index f8995c9..6d3941e 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o obj-$(CONFIG_LEDS_H1940) += leds-h1940.o obj-$(CONFIG_LEDS_COBALT) += leds-cobalt.o obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o +obj-$(CONFIG_LEDS_EM_X270) += leds-em-x270.o # LED Triggers obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o diff --git a/drivers/leds/leds-em-x270.c b/drivers/leds/leds-em-x270.c new file mode 100644 index 0000000..485e7da --- /dev/null +++ b/drivers/leds/leds-em-x270.c @@ -0,0 +1,99 @@ +/* + * LED Triggers Core + * + * Copyright 2007 CompuLab, Ltd. + * Author: Mike Rapoport <mike@compulab.co.il> + * + * Based on Corgi leds driver: + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/da9030.h> + +static void em_x270_led_orange_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + int on = 1; + int pwm_chop = 0; + + pr_info("%s: value = %d\n", __FUNCTION__, value); + if (value == LED_OFF) + on = 0; + else if (value == LED_HALF) + pwm_chop = 4; + + da9030_set_led(3, on, 0, 0, pwm_chop); +} + +static struct led_classdev em_x270_orange_led = { + .name = "em_x270:orange", + .default_trigger = "em-x270-battery.0-charging-or-full", + .brightness_set = em_x270_led_orange_set, +}; + +#ifdef CONFIG_PM +static int em_x270_led_suspend(struct platform_device *dev, pm_message_t state) +{ +#ifdef CONFIG_LEDS_TRIGGERS + if (em_x270_orange_led.trigger && strcmp(em_x270_orange_led.trigger->name, "em-x270-battery-charging-or-full")) +#endif + led_classdev_suspend(&em_x270_orange_led); + return 0; +} + +static int em_x270_led_resume(struct platform_device *dev) +{ + led_classdev_resume(&em_x270_orange_led); + return 0; +} +#endif + +static int em_x270_led_probe(struct platform_device *pdev) +{ + return led_classdev_register(&pdev->dev, &em_x270_orange_led); +} + +static int em_x270_led_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&em_x270_orange_led); + return 0; +} + +static struct platform_driver em_x270_led_driver = { + .probe = em_x270_led_probe, + .remove = em_x270_led_remove, +#ifdef CONFIG_PM + .suspend = em_x270_led_suspend, + .resume = em_x270_led_resume, +#endif + .driver = { + .name = "em-x270-led", + }, +}; + +static int __init em_x270_led_init(void) +{ + return platform_driver_register(&em_x270_led_driver); +} + +static void __exit em_x270_led_exit(void) +{ + platform_driver_unregister(&em_x270_led_driver); +} + +module_init(em_x270_led_init); +module_exit(em_x270_led_exit); + +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); +MODULE_DESCRIPTION("EX-X270 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/chips/jedec_probe.c b/drivers/mtd/chips/jedec_probe.c index 58e561e..7050e71 100644 --- a/drivers/mtd/chips/jedec_probe.c +++ b/drivers/mtd/chips/jedec_probe.c @@ -38,7 +38,7 @@ #define MANUFACTURER_ST 0x0020 #define MANUFACTURER_TOSHIBA 0x0098 #define MANUFACTURER_WINBOND 0x00da - +#define CONTINUATION_CODE 0x007f /* AMD */ #define AM29DL800BB 0x22C8 @@ -68,6 +68,10 @@ #define AT49BV32X 0x00C8 #define AT49BV32XT 0x00C9 +/* Eon */ +#define EN29SL800BB 0x226B +#define EN29SL800BT 0x22EA + /* Fujitsu */ #define MBM29F040C 0x00A4 #define MBM29LV650UE 0x22D7 @@ -632,6 +636,40 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x02000,8) } }, { + .mfr_id = 0x1c, + .dev_id = EN29SL800BT, + .name = "Eon EN29SL800BT", + .uaddr = { + [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */ + [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ + }, + .DevSize = SIZE_1MiB, + .CmdSet = P_ID_AMD_STD, + .NumEraseRegions= 4, + .regions = { + ERASEINFO(0x10000, 15), + ERASEINFO(0x08000, 1), + ERASEINFO(0x02000, 2), + ERASEINFO(0x04000, 1), + } + }, { + .mfr_id = 0x1c, + .dev_id = EN29SL800BB, + .name = "Eon EN29SL800BB", + .uaddr = { + [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */ + [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */ + }, + .DevSize = SIZE_1MiB, + .CmdSet = P_ID_AMD_STD, + .NumEraseRegions= 4, + .regions = { + ERASEINFO(0x04000, 1), + ERASEINFO(0x02000, 2), + ERASEINFO(0x08000, 1), + ERASEINFO(0x10000, 15), + } + }, { .mfr_id = MANUFACTURER_FUJITSU, .dev_id = MBM29F040C, .name = "Fujitsu MBM29F040C", @@ -1769,9 +1807,21 @@ static inline u32 jedec_read_mfr(struct map_info *map, __u32 base, { map_word result; unsigned long mask; - u32 ofs = cfi_build_cmd_addr(0, cfi_interleave(cfi), cfi->device_type); - mask = (1 << (cfi->device_type * 8)) -1; - result = map_read(map, base + ofs); + int bank = 0; + + /* According to JEDEC "Standard Manufacturer's Identification Code" + * (http://www.jedec.org/download/search/jep106W.pdf) + * several first banks can contain 0x7f instead of actual ID + */ + do { + u32 ofs = cfi_build_cmd_addr(0 + (bank << 8), + cfi_interleave(cfi), + cfi->device_type); + mask = (1 << (cfi->device_type * 8)) -1; + result = map_read(map, base + ofs); + bank++; + } while ((result.x[0] & mask) == CONTINUATION_CODE); + return result.x[0] & mask; } diff --git a/drivers/net/dm9000.c b/drivers/net/dm9000.c index 738aa59..88dbbdf 100644 --- a/drivers/net/dm9000.c +++ b/drivers/net/dm9000.c @@ -546,6 +546,7 @@ dm9000_probe(struct platform_device *pdev) if (id_val != DM9000_ID) { printk("%s: wrong id: 0x%08x\n", CARDNAME, id_val); + ret = -ENODEV; goto release; } diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 58c806e..3a16d9c 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -49,4 +49,10 @@ config BATTERY_OLPC help Say Y to enable support for the battery on the OLPC laptop. +config BATTERY_EM_X270 + tristate "EM-X270 battery" + depends on DA9030 && MACH_EM_X270 + help + Say Y here to enable support for battery on EM-X270. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 6413ded..dc9585e 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_APM_POWER) += apm_power.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o +obj-$(CONFIG_BATTERY_EM_X270) += em_x270_battery.o diff --git a/drivers/power/em_x270_battery.c b/drivers/power/em_x270_battery.c new file mode 100644 index 0000000..2630c68 --- /dev/null +++ b/drivers/power/em_x270_battery.c @@ -0,0 +1,579 @@ +/* + * Power managemnet implementation for EM-X270 + * + * Copyright (C) 2007 CompuLab, Ltd. + * Author: Mike Rapoport <mike@compulab.co.il> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#define DEBUG + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pda_power.h> +#include <linux/apm-emulation.h> +#include <linux/da9030.h> +#include <linux/power_supply.h> +#include <linux/pm.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include "../i2c/chips/da9030.h" + +#define VOLTAGE_MAX_DESIGN 4200000 /* 4.2V in uV */ +#define VOLTAGE_MIN_DESIGN 3000000 /* 3V in uV */ + +/* #define VCHARGE_MIN_THRESHOLD 72 /\* 4.5V *\/ */ + +#define VCHARGE_MIN_THRESHOLD 60 /* ?.?V */ +#define VCHARGE_MAX_THRESHOLD 89 /* 5.5V */ +#define TBAT_LOW_THRESHOLD 197 /* 0oC */ +#define TBAT_HIGH_THRESHOLD 78 /* 45oC */ +#define TBAT_RESUME_THRESHOLD 100 /* 35oC */ +#define VBAT_LOW_THRESHOLD 82 /* 3.498V */ +#define VBAT_CRIT_THRESHOLD 62 /* 3.291V */ + +struct da9030_charger { + struct device *dev; + + struct power_supply bat; + struct da9030_adc_res adc; + struct delayed_work work; + + int interval; + int status; + int mA; + int mV; + + int is_charging:1; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debug_file; +#endif +}; + +static unsigned short tbat_readings[] = { + 300, 244, 200, 178, 163, 152, 144, 137, 131, + 126, 122, 118, 114, 111, 108, 105, 103, 101, + 98, 96, 94, 93, 91, 89, 88, 86, 85, + 83, 82, 81, 79, 78, 77, 76, 75, 74, + 73, 72, 71, 70, 69, 68, 67, 67, 66, + 65, 64, 63, 63, 62, 61, 60, 60, 59, + 58, 58, 57, 57, 56, 55, 55, 54, 53, + 53, 52, 52, 51, 51, 50, 50, 49, 49, + 48, 48, 47, 47, 46, 46, 45, 45, 44, + 44, 43, 43, 42, 42, 41, 41, 41, 40, + 40, 39, 39, 38, 38, 38, 37, 37, 36, + 36, 35, 35, 35, 34, 34, 34, 33, 33, + 32, 32, 32, 31, 31, 30, 30, 30, 29, + 29, 29, 28, 28, 28, 27, 27, 26, 26, + 26, 25, 25, 25, 24, 24, 24, 23, 23, + 23, 22, 22, 21, 21, 21, 20, 20, 20, + 19, 19, 19, 18, 18, 18, 17, 17, 17, + 16, 16, 16, 15, 15, 14, 14, 14, 13, + 13, 13, 12, 12, 12, 11, 11, 11, 10, + 10, 10, 9, 9, 8, 8, 8, 7, 7, + 7, 6, 6, 5, 5, 5, 4, 4, 3, + 3, 3, 2, 2, 1, 1, 1, 0, 0, + -1, -1, -1, -2, -2, -3, -3, -4, -4, + -5, -5, -6, -6, -6, -7, -7, -8, -9, + -9, -10, -10, -11, -11, -12, -12, -13, -14, + -14, -15, -16, -16, -17, -18, -18, -19, -20, + -21, -21, -22, -23, -24, -25, -26, -27, -28, + -30, -31, -32, -34, -35, -37, -39, -41, -44, + -47, -50, -56, -64, +}; + +#ifdef CONFIG_DEBUG_FS + +#define REG2VOLT(x) ((((x) * 2650) >> 8) + 2650) + +static int debug_show(struct seq_file *s, void *data) +{ + struct da9030_charger *charger = s->private; + + seq_printf(s, "charger is %s\n", + charger->status & CHRG_CHARGER_ENABLE ? "on" : "off"); + if (charger->status & CHRG_CHARGER_ENABLE) { + seq_printf(s, "iset = %dmA, vset = %dmV\n", + charger->mA, charger->mV); + } + + seq_printf(s, "vbat_res = %d (%dmV)\n", + charger->adc.vbat_res, REG2VOLT(charger->adc.vbat_res)); + seq_printf(s, "vbatmin_res = %d (%dmV)\n", + charger->adc.vbatmin_res, + REG2VOLT(charger->adc.vbatmin_res)); + seq_printf(s, "vbatmintxon = %d (%dmV)\n", + charger->adc.vbatmintxon, + REG2VOLT(charger->adc.vbatmintxon)); + seq_printf(s, "ichmax_res = %d\n", charger->adc.ichmax_res); + seq_printf(s, "ichmin_res = %d\n", charger->adc.ichmin_res); + seq_printf(s, "ichaverage_res = %d\n", charger->adc.ichaverage_res); + seq_printf(s, "vchmax_res = %d (%dmV)\n", + charger->adc.vchmax_res, + REG2VOLT(charger->adc.vchmax_res)); + seq_printf(s, "vchmin_res = %d (%dmV)\n", + charger->adc.vchmin_res, + REG2VOLT(charger->adc.vchmin_res)); + seq_printf(s, "tbat_res = %d (%doC\n", charger->adc.tbat_res, + tbat_readings[charger->adc.tbat_res]); + seq_printf(s, "adc_in4_res = %d\n", charger->adc.adc_in4_res); + seq_printf(s, "adc_in5_res = %d\n", charger->adc.adc_in5_res); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, debug_show, inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry* da9030_create_debugfs(struct da9030_charger *charger) +{ + charger->debug_file = debugfs_create_file("charger", 0666, 0, charger, + &debug_fops); + return charger->debug_file; +} + +static void da9030_remove_debugfs(struct da9030_charger *charger) +{ + debugfs_remove(charger->debug_file); +} +#else +#define da9030_create_debugfs(x) NULL +#define da9030_remove_debugfs(x) do {} while(0) +#endif + +static void da9030_set_charge(struct da9030_charger *charger, int on) +{ +/* int status = da9030_get_status(); */ + if (on) { + pr_debug("%s: enabling charger\n", __FUNCTION__); + da9030_set_thresholds(TBAT_HIGH_THRESHOLD, + TBAT_RESUME_THRESHOLD, + TBAT_LOW_THRESHOLD); + da9030_set_charger(1, 1000, 4200); + charger->is_charging = 1; + } + else { + /* disable charger */ + pr_debug("%s: disabling charger\n", __FUNCTION__); + da9030_set_charger(0, 0, 0); + charger->is_charging = 0; + } +} + +static void da9030_charging_monitor(struct work_struct *work) +{ + struct da9030_charger *charger; + int is_on; + unsigned int mA, mV; + + charger = container_of(work, struct da9030_charger, work.work); + + da9030_get_charger(&is_on, &mA, &mV); + da9030_read_adc(&charger->adc); + + charger->status = da9030_get_status(); + charger->mA = mA; + charger->mV = mV; + + /* we wake or boot with external power on */ + if (!is_on && (charger->status & STATUS_CHDET)) { + da9030_set_charge(charger, 1); + return; + } + + if (is_on) { +/* pr_info("%s: mA = %d, mV = %d\n", __FUNCTION__, mA, mV); */ +/* pr_info("%s: vchmin_res = %d, vchmax_res = %d\n", */ +/* __FUNCTION__, charger->adc.vchmin_res, */ +/* charger->adc.vchmax_res); */ +/* pr_info("%s: tbat_res = %d\n", */ +/* __FUNCTION__, charger->adc.tbat_res); */ + if (charger->adc.vbat_res > VBAT_LOW_THRESHOLD) { + /* update VBAT threshlods ? */ + da9030_set_reg(VBATMON, VBAT_LOW_THRESHOLD); + } + if (charger->adc.vchmax_res > VCHARGE_MAX_THRESHOLD || + charger->adc.vchmin_res < VCHARGE_MIN_THRESHOLD || + /* Tempreture readings are negative */ + charger->adc.tbat_res < TBAT_HIGH_THRESHOLD || + charger->adc.tbat_res > TBAT_LOW_THRESHOLD) { + /* disable charger */ + da9030_set_charge(charger, 0); + } + } + + /* reschedule for the next time */ + schedule_delayed_work(&charger->work, charger->interval); +} + +void da9030_battery_release(struct device * dev) +{ +} + +static void da9030_external_power_changed(struct power_supply *psy) +{ +/* struct da9030_charger *charger; */ + +/* charger = container_of(psy, struct da9030_charger, bat); */ +/* pr_info("%s:\n", __FUNCTION__); */ +/* da9030_set_charge(charger); */ +} + +struct da9030_battery_thresh { + int voltage; + int percentage; +}; + +static struct da9030_battery_thresh vbat_ranges[] = { + { 150, 100}, + { 149, 99}, + { 148, 98}, + { 147, 98}, + { 146, 97}, + { 145, 96}, + { 144, 96}, + { 143, 95}, + { 142, 94}, + { 141, 93}, + { 140, 92}, + { 139, 91}, + { 138, 90}, + { 137, 90}, + { 136, 89}, + { 135, 88}, + { 134, 88}, + { 133, 87}, + { 132, 86}, + { 131, 85}, + { 130, 83}, + { 129, 82}, + { 128, 81}, + { 127, 81}, + { 126, 80}, + { 125, 75}, + { 124, 74}, + { 123, 73}, + { 122, 70}, + { 121, 66}, + { 120, 65}, + { 119, 64}, + { 118, 64}, + { 117, 63}, + { 116, 59}, + { 115, 58}, + { 114, 57}, + { 113, 57}, + { 112, 56}, + { 111, 50}, + { 110, 49}, + { 109, 49}, + { 108, 48}, + { 107, 48}, + { 106, 33}, + { 105, 32}, + { 104, 32}, + { 103, 32}, + { 102, 31}, + { 101, 16}, + { 100, 15}, + { 99, 15}, + { 98, 15}, + { 97, 10}, + { 96, 9}, + { 95, 7}, + { 94, 3}, + { 93, 0}, +}; + +static enum power_supply_property da9030_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, /* in percents! */ + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static void da9030_bat_check_status(union power_supply_propval *val) +{ + int charger_on; + int status = da9030_get_status(); + + da9030_get_charger(&charger_on, 0, 0); + + /* FIXME: below code is very crude approximation of actual + states, we need to take into account voltage and current + measurements to determine actual charger state */ + if (status & STATUS_CHDET) { + if (charger_on) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + } + else { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } + else { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } +} + +static void da9030_bat_check_health(union power_supply_propval *val) +{ + int fault_log = da9030_get_fault_log(); + + if (fault_log & FAULT_LOG_OVER_TEMP) { + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } + else if (fault_log & FAULT_LOG_VBAT_OVER) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } + else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } +} + +static int vbat_interpolate(int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vbat_ranges); i++ ) + if (vbat_ranges[i].voltage == reg) { + pr_debug("%s: voltage = %d, percentage = %d\n", + __FUNCTION__, vbat_ranges[i].voltage, + vbat_ranges[i].percentage); + return vbat_ranges[i].percentage; + } + + return 0; +} + +static int da9030_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + u32 reg; + struct da9030_charger *charger; + charger = container_of(psy, struct da9030_charger, bat); + + switch(psp) { + case POWER_SUPPLY_PROP_STATUS: + da9030_bat_check_status(val); + break; + case POWER_SUPPLY_PROP_HEALTH: + da9030_bat_check_health(val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = VOLTAGE_MAX_DESIGN; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = VOLTAGE_MIN_DESIGN; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + reg = charger->adc.vbat_res; + /* V = (reg / 256) * 2.65 + 2.65 (V) */ + val->intval = ((reg * 2650000) >> 8) + 2650000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + reg = charger->adc.ichaverage_res; + val->intval = reg; /* reg */ + break; + case POWER_SUPPLY_PROP_CAPACITY: + reg = charger->adc.vbat_res; + val->intval = vbat_interpolate(reg); + break; + case POWER_SUPPLY_PROP_TEMP: + reg = charger->adc.tbat_res; + val->intval = tbat_readings[reg]; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "MaxPower"; + pr_debug("%s: MFG = %s\n", __FUNCTION__, val->strval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "LP555597P6H-FPS"; + pr_debug("%s: MODEL = %s\n", __FUNCTION__, val->strval); + break; + default: break; + } + + return 0; +} + +static void da9030_setup_battery(struct power_supply *bat) +{ + bat->name = "em-x270-battery"; + bat->type = POWER_SUPPLY_TYPE_BATTERY; + bat->properties = da9030_bat_props; + bat->num_properties = ARRAY_SIZE(da9030_bat_props); + bat->get_property = da9030_bat_get_property; + bat->use_for_apm = 1; + bat->external_power_changed = da9030_external_power_changed; +}; + +static void da9030_chiover_callback(int event, void *_charger) +{ + /* disable charger */ + struct da9030_charger *charger = _charger; + da9030_set_charge(charger, 0); +} + +static void da9030_tbat_callback(int event, void *_charger) +{ + /* disable charger */ + struct da9030_charger *charger = _charger; + da9030_set_charge(charger, 0); +} + +static void da9030_vbat_callback(int event, void *_charger) +{ + struct da9030_charger *charger = _charger; + da9030_read_adc(&charger->adc); + + if (charger->is_charging) { + if (charger->adc.vbat_res < VBAT_LOW_THRESHOLD) { + /* set VBAT threshold for critical */ + da9030_set_reg(VBATMON, VBAT_CRIT_THRESHOLD); + } + else if (charger->adc.vbat_res < VBAT_CRIT_THRESHOLD) { + /* notify the system of battery critical */ + apm_queue_event(APM_CRITICAL_SUSPEND); + } + } +} + +static void da9030_ccto_callback(int event, void *_charger) +{ + /* x3 */ +} + +static void da9030_tcto_callback(int event, void *_charger) +{ + /* x3 */ +} + +static void da9030_chdet_callback(int event, void *_charger) +{ + struct da9030_charger *charger = _charger; + int status = da9030_get_status(); + da9030_set_charge(charger, !!(status & CHRG_CHARGER_ENABLE)); +} + +static int da9030_battery_probe(struct platform_device *pdev) +{ + struct da9030_charger *charger; + + pr_debug("%s\n", __FUNCTION__); + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (charger == NULL) { + return -ENOMEM; + } + + charger->dev = &pdev->dev; + + charger->interval = 10 * HZ; /* 10 seconds between monotor runs */ + da9030_setup_battery(&charger->bat); + + platform_set_drvdata(pdev, charger); + + da9030_enable_adc(); + + INIT_DELAYED_WORK(&charger->work, da9030_charging_monitor); + schedule_delayed_work(&charger->work, charger->interval); + + charger->debug_file = da9030_create_debugfs(charger); + + da9030_setup_battery(&charger->bat); + + da9030_register_callback(DA9030_IRQ_CHDET, + da9030_chdet_callback, + charger); + da9030_register_callback(DA9030_IRQ_VBATMON, + da9030_vbat_callback, + charger); + + /* critical condition events */ + da9030_register_callback(DA9030_IRQ_CHIOVER, + da9030_chiover_callback, + charger); + da9030_register_callback(DA9030_IRQ_TBAT, + da9030_tbat_callback, + charger); + + /* timer events */ + da9030_register_callback(DA9030_IRQ_TCTO, + da9030_tcto_callback, + charger); + da9030_register_callback(DA9030_IRQ_CCTO, + da9030_ccto_callback, + charger); + + power_supply_register(&pdev->dev, &charger->bat); + + return 0; +} + +static int da9030_battery_remove(struct platform_device *dev) +{ + struct da9030_charger *charger = platform_get_drvdata(dev); + + pr_debug("%s\n", __FUNCTION__); + da9030_remove_debugfs(charger); + cancel_delayed_work(&charger->work); + power_supply_unregister(&charger->bat); + kfree(charger); + return 0; +} + +static struct platform_driver da9030_battery_driver = { + .driver = { + .name = "da9030-battery", + .owner = THIS_MODULE, + }, + .probe = da9030_battery_probe, + .remove = da9030_battery_remove, +}; + +static int da9030_battery_init(void) +{ + pr_debug("%s\n", __FUNCTION__); + + return platform_driver_register(&da9030_battery_driver); +} + +static void da9030_battery_exit(void) +{ + pr_debug("%s\n", __FUNCTION__); + + platform_driver_unregister(&da9030_battery_driver); +} + +module_init(da9030_battery_init); +module_exit(da9030_battery_exit); + +MODULE_DESCRIPTION("DA9030 charger driver"); +MODULE_AUTHOR("Mike Rapoport"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 767aed5..4c44a7a 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -195,6 +195,26 @@ config USB_M66592 default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_PXA27X + boolean "PXA 27x" + depends on ARCH_PXA && PXA27x + help + Intel's PXA 27x series XScale ARM-5TE processors include + an integrated full speed USB 1.1 device controller. + + It has 23 endpoints, as well as endpoint zero (for control + transfers). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "pxa27x_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_PXA27X + tristate + depends on USB_GADGET_PXA27X + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_GOKU boolean "Toshiba TC86C001 'Goku-S'" depends on PCI diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 1bc0f03..b8743bf 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o obj-$(CONFIG_USB_NET2280) += net2280.o obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc.o obj-$(CONFIG_USB_PXA2XX) += pxa2xx_udc.o +obj-$(CONFIG_USB_PXA27X) += pxa27x_udc.o obj-$(CONFIG_USB_GOKU) += goku_udc.o obj-$(CONFIG_USB_OMAP) += omap_udc.o obj-$(CONFIG_USB_LH7A40X) += lh7a40x_udc.o diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index 3aa46cf..d7d5550 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -228,14 +228,19 @@ find_ep (struct usb_gadget *gadget, const char *name) * * On failure, this returns a null endpoint descriptor. */ -struct usb_ep * __devinit usb_ep_autoconfig ( +struct usb_ep * usb_ep_autoconfig ( struct usb_gadget *gadget, - struct usb_endpoint_descriptor *desc + struct usb_endpoint_descriptor *desc, + struct usb_endpoint_config *epconfig, int numconfigs ) { struct usb_ep *ep; u8 type; + /* Use device specific ep allocation code if provided */ + if (gadget->ops->ep_alloc) + return gadget->ops->ep_alloc(gadget, desc, epconfig, numconfigs); + type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; /* First, apply chip-specific "best usage" knowledge. diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 593e235..87aa9fb 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -1350,6 +1350,10 @@ static void rndis_response_complete (struct usb_ep *ep, struct usb_request *req) /* done sending after USB_CDC_GET_ENCAPSULATED_RESPONSE */ } +#ifdef CONFIG_USB_GADGET_PXA27X +int write_ep0_zlp(void); +#endif + static void rndis_command_complete (struct usb_ep *ep, struct usb_request *req) { struct eth_dev *dev = ep->driver_data; @@ -1360,6 +1364,10 @@ static void rndis_command_complete (struct usb_ep *ep, struct usb_request *req) status = rndis_msg_parser (dev->rndis_config, (u8 *) req->buf); if (status < 0) ERROR(dev, "%s: rndis parse error %d\n", __FUNCTION__, status); + +#ifdef CONFIG_USB_GADGET_PXA27X + write_ep0_zlp(); +#endif spin_unlock(&dev->lock); } @@ -2287,7 +2295,8 @@ eth_bind (struct usb_gadget *gadget) struct eth_dev *dev; struct net_device *net; u8 cdc = 1, zlp = 1, rndis = 1; - struct usb_ep *in_ep, *out_ep, *status_ep = NULL; + struct usb_ep *in_ep = NULL , *out_ep = NULL, *status_ep = NULL; + struct usb_endpoint_config ep_config[2]; int status = -ENOMEM; int gcnum; @@ -2386,7 +2395,30 @@ eth_bind (struct usb_gadget *gadget) /* all we really need is bulk IN/OUT */ usb_ep_autoconfig_reset (gadget); - in_ep = usb_ep_autoconfig (gadget, &fs_source_desc); + + ep_config[0].config = DEV_CONFIG_VALUE; +#if defined(DEV_CONFIG_CDC) + ep_config[0].interface = data_intf.bInterfaceNumber; + ep_config[0].altinterface = data_intf.bAlternateSetting; +#else /* DEV_CONFIG_SUBSET */ + ep_config[0].interface = subset_data_intf.bInterfaceNumber; + ep_config[0].altinterface = subset_data_intf.bAlternateSetting; +#endif + +#ifdef CONFIG_USB_ETH_RNDIS + ep_config[1].config = DEV_RNDIS_CONFIG_VALUE; +#ifdef CONFIG_USB_GADGET_PXA27X + ep_config[1].interface = 0; +#else + ep_config[1].interface = rndis_data_intf.bInterfaceNumber; +#endif + ep_config[1].altinterface = rndis_data_intf.bAlternateSetting; + + in_ep = usb_ep_autoconfig(gadget, &fs_source_desc, &ep_config[0], 2); +#else + in_ep = usb_ep_autoconfig(gadget, &fs_source_desc, &ep_config[0], 1); +#endif + if (!in_ep) { autoconf_fail: dev_err (&gadget->dev, @@ -2396,7 +2428,12 @@ autoconf_fail: } in_ep->driver_data = in_ep; /* claim */ - out_ep = usb_ep_autoconfig (gadget, &fs_sink_desc); +#ifdef CONFIG_USB_ETH_RNDIS + out_ep = usb_ep_autoconfig(gadget, &fs_sink_desc, &ep_config[0], 2); +#else + out_ep = usb_ep_autoconfig(gadget, &fs_sink_desc, &ep_config[0], 1); +#endif + if (!out_ep) goto autoconf_fail; out_ep->driver_data = out_ep; /* claim */ @@ -2406,7 +2443,25 @@ autoconf_fail: * Since some hosts expect one, try to allocate one anyway. */ if (cdc || rndis) { - status_ep = usb_ep_autoconfig (gadget, &fs_status_desc); +#ifdef DEV_CONFIG_CDC + ep_config[0].config = DEV_CONFIG_VALUE; + ep_config[0].interface = control_intf.bInterfaceNumber; + ep_config[0].altinterface = control_intf.bAlternateSetting; +#endif +#ifdef CONFIG_USB_ETH_RNDIS + ep_config[1].config = DEV_RNDIS_CONFIG_VALUE; + ep_config[1].interface = rndis_control_intf.bInterfaceNumber; + ep_config[1].altinterface = rndis_control_intf.bAlternateSetting; +#endif + +#if defined(DEV_CONFIG_CDC) && defined(CONFIG_USB_ETH_RNDIS) + status_ep = usb_ep_autoconfig(gadget, &fs_status_desc, &ep_config[0], 2); +#elif defined(CONFIG_USB_ETH_RNDIS) + status_ep = usb_ep_autoconfig(gadget, &fs_status_desc, &ep_config[1], 1); +#else + status_ep = usb_ep_autoconfig(gadget, &fs_status_desc, &ep_config[0], 1); +#endif + if (status_ep) { status_ep->driver_data = status_ep; /* claim */ } else if (rndis) { diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index 965ad7b..b9cd8c9 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -3841,6 +3841,7 @@ static int __init fsg_bind(struct usb_gadget *gadget) struct usb_ep *ep; struct usb_request *req; char *pathbuf, *p; + struct usb_endpoint_config ep_config; fsg->gadget = gadget; set_gadget_data(gadget, fsg); @@ -3911,21 +3912,25 @@ static int __init fsg_bind(struct usb_gadget *gadget) } /* Find all the endpoints we will use */ + ep_config.config = CONFIG_VALUE; + ep_config.interface = intf_desc.bInterfaceNumber; + ep_config.altinterface = intf_desc.bAlternateSetting; + usb_ep_autoconfig_reset(gadget); - ep = usb_ep_autoconfig(gadget, &fs_bulk_in_desc); + ep = usb_ep_autoconfig(gadget, &fs_bulk_in_desc, &ep_config, 1); if (!ep) goto autoconf_fail; ep->driver_data = fsg; // claim the endpoint fsg->bulk_in = ep; - ep = usb_ep_autoconfig(gadget, &fs_bulk_out_desc); + ep = usb_ep_autoconfig(gadget, &fs_bulk_out_desc, &ep_config, 1); if (!ep) goto autoconf_fail; ep->driver_data = fsg; // claim the endpoint fsg->bulk_out = ep; if (transport_is_cbi()) { - ep = usb_ep_autoconfig(gadget, &fs_intr_in_desc); + ep = usb_ep_autoconfig(gadget, &fs_intr_in_desc, &ep_config, 1); if (!ep) goto autoconf_fail; ep->driver_data = fsg; // claim the endpoint diff --git a/drivers/usb/gadget/pxa27x_udc.c b/drivers/usb/gadget/pxa27x_udc.c new file mode 100644 index 0000000..d4270d4 --- /dev/null +++ b/drivers/usb/gadget/pxa27x_udc.c @@ -0,0 +1,2387 @@ +/* + * Handles the Intel 27x USB Device Controller (UDC) + * + * Copyright (C) 2002 Intrinsyc, Inc. (Frank Becker) + * Copyright (C) 2003 Robert Schwebel, Pengutronix + * Copyright (C) 2003 Benedikt Spranger, Pengutronix + * Copyright (C) 2003 David Brownell + * Copyright (C) 2003 Joshua Wise + * Copyright (C) 2004 Intel Corporation + * Copyright (C) 2005 SDG Systems, LLC (Aric Blumer) + * Copyright (C) 2005-2006 Openedhand Ltd. (Richard Purdie) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#undef DEBUG +//#define DEBUG 1 +//#define VERBOSE DBG_VERBOSE + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <linux/version.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/mm.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> + +#include <asm/byteorder.h> +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/mach-types.h> +#include <asm/unaligned.h> +#include <asm/hardware.h> +#include <asm/arch/pxa-regs.h> + +#include <linux/usb/ch9.h> +#include <linux/usb_gadget.h> + +#include <asm/arch/udc.h> + +/* + * This driver handles the USB Device Controller (UDC) in Intel's PXA 27x + * series processors. + * + * Such controller drivers work with a gadget driver. The gadget driver + * returns descriptors, implements configuration and data protocols used + * by the host to interact with this device, and allocates endpoints to + * the different protocol interfaces. The controller driver virtualizes + * usb hardware so that the gadget drivers will be more portable. + * + * This UDC hardware wants to implement a bit too much USB protocol. The + * biggest issue is that the endpoints have to be setup before the controller + * can be enabled and each endpoint can only have one configuration, interface + * and alternative interface number. Once enabled, these cannot be changed + * without a controller reset. + * + * Intel Errata #22 mentions issues when changing alternate interface. + * The exact meaning of this remains uncertain as gadget drivers using alternate + * interfaces such as CDC-Ethernet appear to work... + */ + +#define DRIVER_VERSION "01-01-2006" +#define DRIVER_DESC "PXA 27x USB Device Controller driver" + +static const char driver_name [] = "pxa27x_udc"; + +static const char ep0name [] = "ep0"; + + +#define USE_DMA +//#undef USE_DMA + +#ifdef CONFIG_PROC_FS +#define UDC_PROC_FILE +#endif + +#include "pxa27x_udc.h" + +#ifdef USE_DMA +static int use_dma = 1; +module_param(use_dma, bool, 0); +MODULE_PARM_DESC(use_dma, "true to use dma"); + +static void dma_nodesc_handler(int dmach, void *_ep); +static void kick_dma(struct pxa27x_ep *ep, struct pxa27x_request *req); + +#define DMASTR " (dma support)" + +#else /* !USE_DMA */ +#define DMASTR " (pio only)" +#endif + +#define UDCISR0_IR0 0x3 +#define UDCISR_INT_MASK (UDC_INT_FIFOERROR | UDC_INT_PACKETCMP) +#define UDCICR_INT_MASK UDCISR_INT_MASK + +#define UDCCSR_MASK (UDCCSR_FST | UDCCSR_DME) + +static void pxa27x_ep_fifo_flush(struct usb_ep *ep); +static void nuke(struct pxa27x_ep *, int status); +static void udc_init_ep(struct pxa27x_udc *dev); + + +/* + * Endpoint Functions + */ +static void pio_irq_enable(int ep_num) +{ + if (ep_num < 16) + UDCICR0 |= 3 << (ep_num * 2); + else { + ep_num -= 16; + UDCICR1 |= 3 << (ep_num * 2); + } +} + +static void pio_irq_disable(int ep_num) +{ + ep_num &= 0xf; + if (ep_num < 16) + UDCICR0 &= ~(3 << (ep_num * 2)); + else { + ep_num -= 16; + UDCICR1 &= ~(3 << (ep_num * 2)); + } +} + +/* The UDCCR reg contains mask and interrupt status bits, + * so using '|=' isn't safe as it may ack an interrupt. + */ +#define UDCCR_MASK_BITS (UDCCR_OEN | UDCCR_UDE) + +static inline void udc_set_mask_UDCCR(int mask) +{ + UDCCR = (UDCCR & UDCCR_MASK_BITS) | (mask & UDCCR_MASK_BITS); +} + +static inline void udc_clear_mask_UDCCR(int mask) +{ + UDCCR = (UDCCR & UDCCR_MASK_BITS) & ~(mask & UDCCR_MASK_BITS); +} + +static inline void udc_ack_int_UDCCR(int mask) +{ + /* udccr contains the bits we dont want to change */ + __u32 udccr = UDCCR & UDCCR_MASK_BITS; + + UDCCR = udccr | (mask & ~UDCCR_MASK_BITS); +} + +/* + * Endpoint enable/disable + * + * Not much to do here as the ep_alloc function sets up most things. Once + * enabled, not much of the pxa27x configuration can be changed. + * + */ +static int pxa27x_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct pxa27x_virt_ep *virt_ep = container_of(_ep, struct pxa27x_virt_ep, usb_ep); + struct pxa27x_ep *ep = virt_ep->pxa_ep; + struct pxa27x_udc *dev; + + if (!_ep || !desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->fifo_size < le16_to_cpu(desc->wMaxPacketSize)) { + dev_err(ep->dev->dev, "%s, bad ep or descriptor\n", __FUNCTION__); + return -EINVAL; + } + + /* xfer types must match, except that interrupt ~= bulk */ + if( ep->ep_type != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + dev_err(ep->dev->dev, "%s, %s type mismatch\n", __FUNCTION__, _ep->name); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && le16_to_cpu (desc->wMaxPacketSize) + != BULK_FIFO_SIZE) + || !desc->wMaxPacketSize) { + dev_err(ep->dev->dev, "%s, bad %s maxpacket\n", __FUNCTION__, _ep->name); + return -ERANGE; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + dev_err(ep->dev->dev, "%s, bogus device state\n", __FUNCTION__); + return -ESHUTDOWN; + } + + ep->desc = desc; + ep->dma = -1; + ep->stopped = 0; + ep->pio_irqs = ep->dma_irqs = 0; + ep->usb_ep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); + + /* flush fifo (mostly for OUT buffers) */ + pxa27x_ep_fifo_flush(_ep); + + /* ... reset halt state too, if we could ... */ + +#ifdef USE_DMA + /* for (some) bulk and ISO endpoints, try to get a DMA channel and + * bind it to the endpoint. otherwise use PIO. + */ + dev_dbg(ep->dev->dev, "%s: called attributes=%d\n", __FUNCTION__, ep->ep_type); + switch (ep->ep_type) { + case USB_ENDPOINT_XFER_ISOC: + if (le16_to_cpu(desc->wMaxPacketSize) % 32) + break; + // fall through + case USB_ENDPOINT_XFER_BULK: + if (!use_dma || !ep->reg_drcmr) + break; + ep->dma = pxa_request_dma((char *)_ep->name, (le16_to_cpu(desc->wMaxPacketSize) > 64) + ? DMA_PRIO_MEDIUM : DMA_PRIO_LOW, dma_nodesc_handler, ep); + if (ep->dma >= 0) { + *ep->reg_drcmr = DRCMR_MAPVLD | ep->dma; + dev_dbg(ep->dev->dev, "%s using dma%d\n", _ep->name, ep->dma); + } + default: + break; + } +#endif + DBG(DBG_VERBOSE, "enabled %s\n", _ep->name); + return 0; +} + +static int pxa27x_ep_disable(struct usb_ep *_ep) +{ + struct pxa27x_virt_ep *virt_ep = container_of(_ep, struct pxa27x_virt_ep, usb_ep); + struct pxa27x_ep *ep = virt_ep->pxa_ep; + unsigned long flags; + + if (!_ep || !ep->desc) { + dev_err(ep->dev->dev, "%s, %s not enabled\n", __FUNCTION__, + _ep ? _ep->name : NULL); + return -EINVAL; + } + local_irq_save(flags); + nuke(ep, -ESHUTDOWN); + +#ifdef USE_DMA + if (ep->dma >= 0) { + *ep->reg_drcmr = 0; + pxa_free_dma(ep->dma); + ep->dma = -1; + } +#endif + + /* flush fifo (mostly for IN buffers) */ + pxa27x_ep_fifo_flush(_ep); + + ep->desc = 0; + ep->stopped = 1; + + local_irq_restore(flags); + DBG(DBG_VERBOSE, "%s disabled\n", _ep->name); + return 0; +} + + + +/* for the pxa27x, these can just wrap kmalloc/kfree. gadget drivers + * must still pass correctly initialized endpoints, since other controller + * drivers may care about how it's currently set up (dma issues etc). + */ + +/* + * pxa27x_ep_alloc_request - allocate a request data structure + */ +static struct usb_request * +pxa27x_ep_alloc_request(struct usb_ep *_ep, unsigned gfp_flags) +{ + struct pxa27x_request *req; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return 0; + + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + + +/* + * pxa27x_ep_free_request - deallocate a request data structure + */ +static void +pxa27x_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa27x_request *req; + + req = container_of(_req, struct pxa27x_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + + +/*-------------------------------------------------------------------------*/ + +/* + * done - retire a request; caller blocked irqs + */ +static void done(struct pxa27x_ep *ep, struct pxa27x_request *req, int status) +{ + list_del_init(&req->queue); + if (likely (req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + DBG(DBG_VERBOSE, "complete %s req %p stat %d len %u/%u\n", + ep->usb_ep->name, &req->req, status, + req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + req->req.complete(ep->usb_ep, &req->req); +} + + +static inline void ep0_idle(struct pxa27x_udc *dev) +{ + dev->ep0state = EP0_IDLE; +} + +static int write_packet(volatile u32 *uddr, struct pxa27x_request *req, unsigned max) +{ + u32 *buf; + int length, count, remain; + + buf = (u32*)(req->req.buf + req->req.actual); + prefetch(buf); + + /* how big will this packet be? */ + length = min(req->req.length - req->req.actual, max); + req->req.actual += length; + + remain = length & 0x3; + count = length & ~(0x3); + + //dev_dbg(ep->dev->dev, "Length %d, Remain %d, Count %d\n",length, remain, count); + + while (likely(count)) { + //dev_dbg(ep->dev->dev, "Sending:0x%x\n", *buf); + *uddr = *buf++; + count -= 4; + } + + if (remain) { + volatile u8* reg=(u8*)uddr; + char *rd =(u8*)buf; + + while (remain--) { + *reg=*rd++; + } + } + + return length; +} + +/* + * write to an IN endpoint fifo, as many packets as possible. + * irqs will use this to write the rest later. + * caller guarantees at least one packet buffer is ready (or a zlp). + */ +static int +write_fifo(struct pxa27x_ep *ep, struct pxa27x_request *req) +{ + unsigned max; + + max = le16_to_cpu(ep->desc->wMaxPacketSize); + do { + int count, is_last, is_short; + + //dev_dbg(ep->dev->dev, "write_fifo7 %x\n", *ep->reg_udccsr); + + if (*ep->reg_udccsr & UDCCSR_PC) { + //dev_dbg(ep->dev->dev, "Transmit Complete\n"); + *ep->reg_udccsr = UDCCSR_PC | (*ep->reg_udccsr & UDCCSR_MASK); + } + + if (*ep->reg_udccsr & UDCCSR_TRN) { + //dev_dbg(ep->dev->dev, "Clearing Underrun\n"); + *ep->reg_udccsr = UDCCSR_TRN | (*ep->reg_udccsr & UDCCSR_MASK); + } + //dev_dbg(ep->dev->dev, "write_fifo8 %x\n", *ep->reg_udccsr); + + count = write_packet(ep->reg_udcdr, req, max); + + /* last packet is usually short (or a zlp) */ + if (unlikely (count != max)) + is_last = is_short = 1; + else { + if (likely(req->req.length != req->req.actual) + || req->req.zero) + is_last = 0; + else + is_last = 1; + /* interrupt/iso maxpacket may not fill the fifo */ + is_short = unlikely (max < ep->fifo_size); + } + + //dev_dbg(ep->dev->dev, "write_fifo0 %x\n", *ep->reg_udccsr); + + dev_dbg(ep->dev->dev, "wrote %s count:%d bytes%s%s %d left %p\n", + ep->usb_ep->name, count, + is_last ? "/L" : "", is_short ? "/S" : "", + req->req.length - req->req.actual, &req->req); + + /* let loose that packet. maybe try writing another one, + * double buffering might work. + */ + + if (is_short) + *ep->reg_udccsr = UDCCSR_SP | (*ep->reg_udccsr & UDCCSR_MASK); + + dev_dbg(ep->dev->dev, "write_fifo0.5 %x\n", *ep->reg_udccsr); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + done(ep, req, 0); + if (list_empty(&ep->queue) || unlikely(ep->dma >= 0)) { + pio_irq_disable(ep->pxa_ep_num); + //dev_dbg(ep->dev->dev, "write_fifo1 %x\n", *ep->reg_udccsr); +#ifdef USE_DMA + /* unaligned data and zlps couldn't use dma */ + if (unlikely(!list_empty(&ep->queue))) { + req = list_entry(ep->queue.next, + struct pxa27x_request, queue); + kick_dma(ep,req); + return 0; + } +#endif + } + //dev_dbg(ep->dev->dev, "write_fifo2 %x\n", *ep->reg_udccsr); + return 1; + } + + // TODO experiment: how robust can fifo mode tweaking be? + // double buffering is off in the default fifo mode, which + // prevents TFS from being set here. + + } while (*ep->reg_udccsr & UDCCSR_FS); + //dev_dbg(ep->dev->dev, "write_fifo2 %x\n", *ep->reg_udccsr); + return 0; +} + +/* caller asserts req->pending (ep0 irq status nyet cleared); starts + * ep0 data stage. these chips want very simple state transitions. + */ +static inline +void ep0start(struct pxa27x_udc *dev, u32 flags, const char *tag) +{ + UDCCSR0 = flags|UDCCSR0_SA|UDCCSR0_OPC; + UDCISR0 = UDCICR_INT(0, UDC_INT_FIFOERROR | UDC_INT_PACKETCMP); + dev->req_pending = 0; + DBG(DBG_VERY_NOISY, "%s %s, %02x/%02x\n", + __FUNCTION__, tag, UDCCSR0, flags); +} + +static int +write_ep0_fifo(struct pxa27x_ep *ep, struct pxa27x_request *req) +{ + unsigned count; + int is_short; + + count = write_packet(&UDCDR0, req, EP0_FIFO_SIZE); + ep->dev->stats.write.bytes += count; + + /* last packet "must be" short (or a zlp) */ + is_short = (count != EP0_FIFO_SIZE); + + DBG(DBG_VERY_NOISY, "ep0in %d bytes %d left %p\n", count, + req->req.length - req->req.actual, &req->req); + + if (unlikely (is_short)) { + if (ep->dev->req_pending) + ep0start(ep->dev, UDCCSR0_IPR, "short IN"); + else + UDCCSR0 = UDCCSR0_IPR; + + count = req->req.length; + done(ep, req, 0); + ep0_idle(ep->dev); +#if 0 + /* This seems to get rid of lost status irqs in some cases: + * host responds quickly, or next request involves config + * change automagic, or should have been hidden, or ... + * + * FIXME get rid of all udelays possible... + */ + if (count >= EP0_FIFO_SIZE) { + count = 100; + do { + if ((UDCCSR0 & UDCCSR0_OPC) != 0) { + /* clear OPC, generate ack */ + UDCCSR0 = UDCCSR0_OPC; + break; + } + count--; + udelay(1); + } while (count); + } +#endif + } else if (ep->dev->req_pending) + ep0start(ep->dev, 0, "IN"); + return is_short; +} + + +/* + * read_fifo - unload packet(s) from the fifo we use for usb OUT + * transfers and put them into the request. caller should have made + * sure there's at least one packet ready. + * + * returns true if the request completed because of short packet or the + * request buffer having filled (and maybe overran till end-of-packet). + */ +static int read_fifo(struct pxa27x_ep *ep, struct pxa27x_request *req) +{ + for (;;) { + u32 *buf; + int bufferspace, count, is_short; + + /* make sure there's a packet in the FIFO.*/ + if (unlikely ((*ep->reg_udccsr & UDCCSR_PC) == 0)) + break; + buf =(u32*) (req->req.buf + req->req.actual); + prefetchw(buf); + bufferspace = req->req.length - req->req.actual; + + /* read all bytes from this packet */ + if (likely (*ep->reg_udccsr & UDCCSR_BNE)) { + count = 0x3ff & *ep->reg_udcbcr; + req->req.actual += min(count, bufferspace); + } else /* zlp */ + count = 0; + + is_short = (count < ep->usb_ep->maxpacket); + dev_dbg(ep->dev->dev, "read %s udccsr:%02x, count:%d bytes%s req %p %d/%d\n", + ep->usb_ep->name, *ep->reg_udccsr, count, + is_short ? "/S" : "", + &req->req, req->req.actual, req->req.length); + + count = min(count, bufferspace); + while (likely (count > 0)) { + *buf++ = *ep->reg_udcdr; + count -= 4; + } + dev_dbg(ep->dev->dev, "Buf:0x%p\n", req->req.buf); + + *ep->reg_udccsr = UDCCSR_PC; + /* RPC/RSP/RNE could now reflect the other packet buffer */ + + /* completion */ + if (is_short || req->req.actual == req->req.length) { + done(ep, req, 0); + if (list_empty(&ep->queue)) + pio_irq_disable(ep->pxa_ep_num); + return 1; + } + + /* finished that packet. the next one may be waiting... */ + } + return 0; +} + +/* + * special ep0 version of the above. no UBCR0 or double buffering; status + * handshaking is magic. most device protocols don't need control-OUT. + * CDC vendor commands (and RNDIS), mass storage CB/CBI, and some other + * protocols do use them. + */ +static int read_ep0_fifo(struct pxa27x_ep *ep, struct pxa27x_request *req) +{ + u32 *buf, word; + unsigned bufferspace; + + buf = (u32*) (req->req.buf + req->req.actual); + bufferspace = req->req.length - req->req.actual; + + while (UDCCSR0 & UDCCSR0_RNE) { + word = UDCDR0; + + if (unlikely (bufferspace == 0)) { + /* this happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data. + */ + if (req->req.status != -EOVERFLOW) + dev_info(ep->dev->dev, "%s overflow\n", ep->usb_ep->name); + req->req.status = -EOVERFLOW; + } else { + *buf++ = word; + req->req.actual += 4; + bufferspace -= 4; + } + } + + UDCCSR0 = UDCCSR0_OPC ; + + /* completion */ + if (req->req.actual >= req->req.length) + return 1; + + /* finished that packet. the next one may be waiting... */ + return 0; +} + +#ifdef USE_DMA + +#define MAX_IN_DMA ((DCMD_LENGTH + 1) - BULK_FIFO_SIZE) +static void kick_dma(struct pxa27x_ep *ep, struct pxa27x_request *req) +{ + u32 dcmd = 0; + u32 len = req->req.length; + u32 buf = req->req.dma; + u32 fifo = io_v2p((u32)ep->reg_udcdr); + + buf += req->req.actual; + len -= req->req.actual; + ep->dma_con = 0; + + DMSG("%s: req:0x%p length:%d, actual:%d dma:%d\n", + __FUNCTION__, &req->req, req->req.length, + req->req.actual,ep->dma); + + /* no-descriptor mode can be simple for bulk-in, iso-in, iso-out */ + DCSR(ep->dma) = DCSR_NODESC; + if (buf & 0x3) + DALGN |= 1 << ep->dma; + else + DALGN &= ~(1 << ep->dma); + + if (ep->dir_in) { + DSADR(ep->dma) = buf; + DTADR(ep->dma) = fifo; + if (len > MAX_IN_DMA) { + len= MAX_IN_DMA; + ep->dma_con =1 ; + } else if (len >= ep->usb_ep->maxpacket) { + if ((ep->dma_con = (len % ep->usb_ep->maxpacket) != 0)) + len = ep->usb_ep->maxpacket; + } + dcmd = len | DCMD_BURST32 | DCMD_WIDTH4 | DCMD_ENDIRQEN + | DCMD_FLOWTRG | DCMD_INCSRCADDR; + } else { + DSADR(ep->dma) = fifo; + DTADR(ep->dma) = buf; + dcmd = len | DCMD_BURST32 | DCMD_WIDTH4 | DCMD_ENDIRQEN + | DCMD_FLOWSRC | DCMD_INCTRGADDR; + } + *ep->reg_udccsr = UDCCSR_DME; + DCMD(ep->dma) = dcmd; + DCSR(ep->dma) = DCSR_NODESC | DCSR_EORIRQEN \ + | ((ep->dir_in) ? DCSR_STOPIRQEN : 0); + *ep->reg_drcmr = ep->dma | DRCMR_MAPVLD; + DCSR(ep->dma) |= DCSR_RUN; +} + +static void cancel_dma(struct pxa27x_ep *ep) +{ + struct pxa27x_request *req; + u32 tmp; + + if (DCSR(ep->dma) == 0 || list_empty(&ep->queue)) + return; + + DMSG("hehe dma:%d,dcsr:0x%x\n", ep->dma, DCSR(ep->dma)); + DCSR(ep->dma) = 0; + while ((DCSR(ep->dma) & DCSR_STOPSTATE) == 0) + cpu_relax(); + + req = list_entry(ep->queue.next, struct pxa27x_request, queue); + tmp = DCMD(ep->dma) & DCMD_LENGTH; + req->req.actual = req->req.length - tmp; + + /* the last tx packet may be incomplete, so flush the fifo. + * FIXME correct req.actual if we can + */ + *ep->reg_udccsr = UDCCSR_FEF; +} + +static void dma_nodesc_handler(int dmach, void *_ep) +{ + struct pxa27x_ep *ep = _ep; + struct pxa27x_request *req, *req_next; + u32 dcsr, tmp, completed; + + local_irq_disable(); + + req = list_entry(ep->queue.next, struct pxa27x_request, queue); + + DMSG("%s, buf:0x%p\n",__FUNCTION__, req->req.buf); + + ep->dma_irqs++; + ep->dev->stats.irqs++; + + completed = 0; + + dcsr = DCSR(dmach); + DCSR(ep->dma) &= ~DCSR_RUN; + + if (dcsr & DCSR_BUSERR) { + DCSR(dmach) = DCSR_BUSERR; + dev_err(ep->dev->dev, "DMA Bus Error\n"); + req->req.status = -EIO; + completed = 1; + } else if (dcsr & DCSR_ENDINTR) { + DCSR(dmach) = DCSR_ENDINTR; + if (ep->dir_in) { + tmp = req->req.length - req->req.actual; + /* Last packet is a short one*/ + if (tmp < ep->usb_ep->maxpacket) { + int count = 0; + + *ep->reg_udccsr = UDCCSR_SP | \ + (*ep->reg_udccsr & UDCCSR_MASK); + /*Wait for packet out */ + while( (count++ < 10000) && \ + !(*ep->reg_udccsr & UDCCSR_FS)); + if (count >= 10000) + DMSG("Failed to send packet\n"); + else + DMSG("%s: short packet sent len:%d," + "length:%d,actual:%d\n", __FUNCTION__, + tmp, req->req.length, req->req.actual); + req->req.actual = req->req.length; + completed = 1; + /* There are still packets to transfer */ + } else if ( ep->dma_con) { + DMSG("%s: more packets,length:%d,actual:%d\n", + __FUNCTION__,req->req.length, + req->req.actual); + req->req.actual += ep->usb_ep->maxpacket; + completed = 0; + } else { + DMSG("%s: no more packets,length:%d," + "actual:%d\n", __FUNCTION__, + req->req.length, req->req.actual); + req->req.actual = req->req.length; + completed = 1; + } + } else { + req->req.actual = req->req.length; + completed = 1; + } + } else if (dcsr & DCSR_EORINTR) { //Only happened in OUT DMA + int remain,udccsr ; + + DCSR(dmach) = DCSR_EORINTR; + remain = DCMD(dmach) & DCMD_LENGTH; + req->req.actual = req->req.length - remain; + + udccsr = *ep->reg_udccsr; + if (udccsr & UDCCSR_SP) { + *ep->reg_udccsr = UDCCSR_PC | (udccsr & UDCCSR_MASK); + completed = 1; + } + DMSG("%s: length:%d actual:%d\n", + __FUNCTION__, req->req.length, req->req.actual); + } else + DMSG("%s: Others dma:%d DCSR:0x%x DCMD:0x%x\n", + __FUNCTION__, dmach, DCSR(dmach), DCMD(dmach)); + + if (likely(completed)) { + if (req->queue.next != &ep->queue) { + req_next = list_entry(req->queue.next, + struct pxa27x_request, queue); + kick_dma(ep, req_next); + } + done(ep, req, 0); + } else { + kick_dma(ep, req); + } + + local_irq_enable(); +} + +#endif +/*-------------------------------------------------------------------------*/ + +static int +pxa27x_ep_queue(struct usb_ep *_ep, struct usb_request *_req, unsigned gfp_flags) +{ + struct pxa27x_virt_ep *virt_ep; + struct pxa27x_ep *ep; + struct pxa27x_request *req; + struct pxa27x_udc *dev; + unsigned long flags; + + virt_ep = container_of(_ep, struct pxa27x_virt_ep, usb_ep); + ep = virt_ep->pxa_ep; + + req = container_of(_req, struct pxa27x_request, req); + if (unlikely (!_req || !_req->complete || !_req->buf|| + !list_empty(&req->queue))) { + DMSG("%s, bad params\n", __FUNCTION__); + return -EINVAL; + } + + if (unlikely (!_ep || (!ep->desc && _ep->name != ep0name))) { + DMSG("%s, bad ep\n", __FUNCTION__); + return -EINVAL; + } + + DMSG("%s, ep point %d is queue\n", __FUNCTION__, ep->ep_num); + + dev = ep->dev; + if (unlikely (!dev->driver + || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + DMSG("%s, bogus device state\n", __FUNCTION__); + return -ESHUTDOWN; + } + + /* iso is always one packet per request, that's the only way + * we can report per-packet status. that also helps with dma. + */ + if (unlikely (ep->ep_type == USB_ENDPOINT_XFER_ISOC + && req->req.length > le16_to_cpu + (ep->desc->wMaxPacketSize))) + return -EMSGSIZE; + +#ifdef USE_DMA + // FIXME caller may already have done the dma mapping + if (ep->dma >= 0) { + _req->dma = dma_map_single(dev->dev, _req->buf, _req->length, + (ep->dir_in) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + } +#endif + + DBG(DBG_NOISY, "%s queue req %p, len %d buf %p\n", + _ep->name, _req, _req->length, _req->buf); + + local_irq_save(flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + if (list_empty(&ep->queue) && !ep->stopped) { + if (ep->desc == 0 /* ep0 */) { + unsigned length = _req->length; + + switch (dev->ep0state) { + case EP0_IN_DATA_PHASE: + dev->stats.write.ops++; + if (write_ep0_fifo(ep, req)) + req = 0; + break; + + case EP0_OUT_DATA_PHASE: + dev->stats.read.ops++; + if (dev->req_pending) + ep0start(dev, UDCCSR0_IPR, "OUT"); + if (length == 0 || ((UDCCSR0 & UDCCSR0_RNE) != 0 + && read_ep0_fifo(ep, req))) { + ep0_idle(dev); + done(ep, req, 0); + req = 0; + } + break; + case EP0_NO_ACTION: + ep0_idle(dev); + req=0; + break; + default: + DMSG("ep0 i/o, odd state %d\n", dev->ep0state); + local_irq_restore (flags); + return -EL2HLT; + } +#ifdef USE_DMA + /* either start dma or prime pio pump */ + } else if (ep->dma >= 0) { + kick_dma(ep, req); +#endif + /* can the FIFO can satisfy the request immediately? */ + } else if (ep->dir_in && (*ep->reg_udccsr & UDCCSR_FS) != 0 + && write_fifo(ep, req)) { + req = 0; + } else if ((*ep->reg_udccsr & UDCCSR_FS) != 0 + && read_fifo(ep, req)) { + req = 0; + } + DMSG("req:%p,ep->desc:%p,ep->dma:%d\n", req, ep->desc, ep->dma); + if (likely (req && ep->desc) && ep->dma < 0) + pio_irq_enable(ep->pxa_ep_num); + } + + /* pio or dma irq handler advances the queue. */ + if (likely (req != 0)) + list_add_tail(&req->queue, &ep->queue); + local_irq_restore(flags); + + return 0; +} + + +/* + * nuke - dequeue ALL requests + */ +static void nuke(struct pxa27x_ep *ep, int status) +{ + struct pxa27x_request *req; + + /* called with irqs blocked */ +#ifdef USE_DMA + if (ep->dma >= 0 && !ep->stopped) + cancel_dma(ep); +#endif + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct pxa27x_request, queue); + done(ep, req, status); + } + if (ep->desc) + pio_irq_disable(ep->pxa_ep_num); +} + + +/* dequeue JUST ONE request */ +static int pxa27x_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa27x_virt_ep *virt_ep = container_of(_ep, struct pxa27x_virt_ep, usb_ep); + struct pxa27x_ep *ep = virt_ep->pxa_ep; + struct pxa27x_request *req; + unsigned long flags; + + if (!_ep || _ep->name == ep0name) + return -EINVAL; + + local_irq_save(flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + local_irq_restore(flags); + return -EINVAL; + } + +#ifdef USE_DMA + if (ep->dma >= 0 && ep->queue.next == &req->queue && !ep->stopped) { + cancel_dma(ep); + done(ep, req, -ECONNRESET); + /* restart i/o */ + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct pxa27x_request, queue); + kick_dma(ep, req); + } + } else +#endif + done(ep, req, -ECONNRESET); + + local_irq_restore(flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int pxa27x_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct pxa27x_virt_ep *virt_ep = container_of(_ep, struct pxa27x_virt_ep, usb_ep); + struct pxa27x_ep *ep = virt_ep->pxa_ep; + unsigned long flags; + + DMSG("%s is called\n", __FUNCTION__); + if (unlikely (!_ep || (!ep->desc && _ep->name != ep0name)) + || ep->ep_type == USB_ENDPOINT_XFER_ISOC) { + DMSG("%s, bad ep\n", __FUNCTION__); + return -EINVAL; + } + if (value == 0) { + /* this path (reset toggle+halt) is needed to implement + * SET_INTERFACE on normal hardware. but it can't be + * done from software on the PXA UDC, and the hardware + * forgets to do it as part of SET_INTERFACE automagic. + */ + DMSG("only host can clear %s halt\n", _ep->name); + return -EROFS; + } + + local_irq_save(flags); + + if (ep->dir_in && ((*ep->reg_udccsr & UDCCSR_FS) == 0 + || !list_empty(&ep->queue))) { + local_irq_restore(flags); + return -EAGAIN; + } + + /* FST bit is the same for control, bulk in, bulk out, interrupt in */ + *ep->reg_udccsr = UDCCSR_FST|UDCCSR_FEF; + + /* ep0 needs special care */ + if (!ep->desc) { + start_watchdog(ep->dev); + ep->dev->req_pending = 0; + ep->dev->ep0state = EP0_STALL; + + /* and bulk/intr endpoints like dropping stalls too */ + } else { + unsigned i; + for (i = 0; i < 1000; i += 20) { + if (*ep->reg_udccsr & UDCCSR_SST) + break; + udelay(20); + } + } + local_irq_restore(flags); + + DBG(DBG_VERBOSE, "%s halt\n", _ep->name); + return 0; +} + +static int pxa27x_ep_fifo_status(struct usb_ep *_ep) +{ + struct pxa27x_virt_ep *virt_ep = container_of(_ep, struct pxa27x_virt_ep, usb_ep); + struct pxa27x_ep *ep = virt_ep->pxa_ep; + + if (!_ep) { + DMSG("%s, bad ep\n", __FUNCTION__); + return -ENODEV; + } + /* pxa can't report unclaimed bytes from IN fifos */ + if (ep->dir_in) + return -EOPNOTSUPP; + if (ep->dev->gadget.speed == USB_SPEED_UNKNOWN + || (*ep->reg_udccsr & UDCCSR_FS) == 0) + return 0; + else + return (*ep->reg_udcbcr & 0xfff) + 1; +} + +static void pxa27x_ep_fifo_flush(struct usb_ep *_ep) +{ + struct pxa27x_virt_ep *virt_ep = container_of(_ep, struct pxa27x_virt_ep, usb_ep); + struct pxa27x_ep *ep = virt_ep->pxa_ep; + + DMSG("pxa27x_ep_fifo_flush\n"); + + if (!_ep || _ep->name == ep0name || !list_empty(&ep->queue)) { + DMSG("%s, bad ep\n", __FUNCTION__); + return; + } + + /* toggle and halt bits stay unchanged */ + + /* for OUT, just read and discard the FIFO contents. */ + if (!ep->dir_in) { + while (((*ep->reg_udccsr) & UDCCSR_BNE) != 0) + (void) *ep->reg_udcdr; + return; + } + + /* most IN status is the same, but ISO can't stall */ + *ep->reg_udccsr = UDCCSR_PC|UDCCSR_FST|UDCCSR_TRN + | (ep->ep_type == USB_ENDPOINT_XFER_ISOC) + ? 0 : UDCCSR_SST; +} + + +static struct usb_ep_ops pxa27x_ep_ops = { + .enable = pxa27x_ep_enable, + .disable = pxa27x_ep_disable, + + .alloc_request = pxa27x_ep_alloc_request, + .free_request = pxa27x_ep_free_request, + + .queue = pxa27x_ep_queue, + .dequeue = pxa27x_ep_dequeue, + + .set_halt = pxa27x_ep_set_halt, + .fifo_status = pxa27x_ep_fifo_status, + .fifo_flush = pxa27x_ep_fifo_flush, +}; + + +/* --------------------------------------------------------------------------- + * device-scoped parts of the api to the usb controller hardware + * --------------------------------------------------------------------------- + */ + +static inline unsigned int validate_fifo_size(u8 bmAttributes) +{ + switch (bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + return EP0_FIFO_SIZE; + break; + case USB_ENDPOINT_XFER_ISOC: + return ISO_FIFO_SIZE; + break; + case USB_ENDPOINT_XFER_BULK: + return BULK_FIFO_SIZE; + break; + case USB_ENDPOINT_XFER_INT: + return INT_FIFO_SIZE; + break; + default: + break; + } +} + +static void pxa27x_ep_free(struct usb_gadget *gadget, struct usb_ep *_ep) +{ + struct pxa27x_udc *dev = the_controller; + struct pxa27x_virt_ep *virt_ep; + int i; + + virt_ep = container_of(_ep, struct pxa27x_virt_ep, usb_ep); + + for (i = 1; i < UDC_EP_NUM; i++) { + if (dev->ep[i].usb_ep == &virt_ep->usb_ep) { + if (dev->ep[i].desc) { + virt_ep->pxa_ep = &dev->ep[i]; + pxa27x_ep_disable(&virt_ep->usb_ep); + } + dev->ep[i].usb_ep = NULL; + } + } + + if (!list_empty(&virt_ep->usb_ep.ep_list)) + list_del_init(&virt_ep->usb_ep.ep_list); + + kfree(virt_ep->usb_ep.name); + kfree(virt_ep); +} + +static void pxa27x_ep_freeall(struct usb_gadget *gadget) +{ + struct pxa27x_udc *dev = the_controller; + int i; + + for (i = 1; i < UDC_EP_NUM; i++) { + if(dev->ep[i].usb_ep) + pxa27x_ep_free(gadget, dev->ep[i].usb_ep); + } +} + +#define NAME_SIZE 18 + +static int pxa27x_find_free_ep(struct pxa27x_udc *dev) +{ + int i; + for (i = 1; i < UDC_EP_NUM; i++) { + if(!dev->ep[i].assigned) + return i; + } + return -1; +} + +/* + * Endpoint Allocation/Configuration + * + * pxa27x endpoint configuration is fixed when the device is enabled. Any pxa + * endpoint is only active in one configuration, interface and alternate + * interface combination so to support gadget drivers, we map one usb_ep to + * one of several pxa ep's. One pxa endpoint is assigned per configuration + * combination. + */ +static struct usb_ep* pxa27x_ep_alloc(struct usb_gadget *gadget, struct usb_endpoint_descriptor *desc, + struct usb_endpoint_config *epconfig, int configs) +{ + struct pxa27x_udc *dev = the_controller; + struct pxa27x_virt_ep *virt_ep; + unsigned int i, fifo_size; + char *name; + + if (unlikely(configs < 1)) { + dev_err(dev->dev, "%s: Error in config data\n", __FUNCTION__); + return NULL; + } + + virt_ep = kmalloc(sizeof(struct pxa27x_virt_ep), GFP_KERNEL); + name = kmalloc(NAME_SIZE, GFP_KERNEL); + if (!virt_ep || !name) { + dev_err(dev->dev, "%s: -ENOMEM\n", __FUNCTION__); + kfree(name); + kfree(virt_ep); + return NULL; + } + + if (!(desc->wMaxPacketSize)) { + fifo_size = validate_fifo_size(desc->bmAttributes); + desc->wMaxPacketSize = fifo_size; + } else { + fifo_size = desc->wMaxPacketSize; + } + + DMSG("pxa27x_ep_alloc: bLength: %d, bDescriptorType: %x, bEndpointAddress: %x,\n" + " bmAttributes: %x, wMaxPacketSize: %d\n", desc->bLength, + desc->bDescriptorType, desc->bEndpointAddress, desc->bmAttributes, + desc->wMaxPacketSize); + + if (!(desc->bEndpointAddress & 0xF)) + desc->bEndpointAddress |= dev->ep_num; + + for (i = 0; i < configs; i++) + { + struct pxa27x_ep *pxa_ep; + int j; + + DMSG("pxa27x_ep_alloc: config: %d, interface: %d, altinterface: %x,\n", + epconfig->config, epconfig->interface, epconfig->altinterface); + + j = pxa27x_find_free_ep(dev); + + if (unlikely(j < 0)) { + dev_err(dev->dev, "pxa27x_ep_alloc: Failed to find a spare endpoint\n"); + pxa27x_ep_free(gadget, &virt_ep->usb_ep); + return NULL; + } + + pxa_ep = &dev->ep[j]; + + if (i == 0) + virt_ep->pxa_ep = pxa_ep; + + pxa_ep->assigned = 1; + pxa_ep->ep_num = dev->ep_num; + pxa_ep->pxa_ep_num = j; + pxa_ep->usb_ep = &virt_ep->usb_ep; + pxa_ep->dev = dev; + pxa_ep->desc = desc; + pxa_ep->pio_irqs = pxa_ep->dma_irqs = 0; + pxa_ep->dma = -1; + + pxa_ep->fifo_size = fifo_size; + pxa_ep->dir_in = (desc->bEndpointAddress & USB_DIR_IN) ? 1 : 0; + pxa_ep->ep_type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + pxa_ep->stopped = 1; + pxa_ep->dma_con = 0; + pxa_ep->config = epconfig->config; + pxa_ep->interface = epconfig->interface; + pxa_ep->aisn = epconfig->altinterface; + + pxa_ep->reg_udccsr = &UDCCSR0 + j; + pxa_ep->reg_udcbcr = &UDCBCR0 + j; + pxa_ep->reg_udcdr = &UDCDR0 + j ; + pxa_ep->reg_udccr = &UDCCRA - 1 + j; +#ifdef USE_DMA + pxa_ep->reg_drcmr = &DRCMR24 + j; +#endif + + /* Configure UDCCR */ + *pxa_ep->reg_udccr = ((pxa_ep->config << UDCCONR_CN_S) & UDCCONR_CN) + | ((pxa_ep->interface << UDCCONR_IN_S) & UDCCONR_IN) + | ((pxa_ep->aisn << UDCCONR_AISN_S) & UDCCONR_AISN) + | ((dev->ep_num << UDCCONR_EN_S) & UDCCONR_EN) + | ((pxa_ep->ep_type << UDCCONR_ET_S) & UDCCONR_ET) + | ((pxa_ep->dir_in) ? UDCCONR_ED : 0) + | ((min(pxa_ep->fifo_size, (unsigned)desc->wMaxPacketSize) << UDCCONR_MPS_S ) & UDCCONR_MPS) + | UDCCONR_EE; +// | UDCCONR_DE | UDCCONR_EE; + + + +#ifdef USE_DMA + /* Only BULK use DMA */ + if ((pxa_ep->ep_type & USB_ENDPOINT_XFERTYPE_MASK)\ + == USB_ENDPOINT_XFER_BULK) + *pxa_ep->reg_udccsr = UDCCSR_DME; +#endif + + DMSG("UDCCR: 0x%p is 0x%x\n", pxa_ep->reg_udccr,*pxa_ep->reg_udccr); + + epconfig++; + } + + /* Fill ep name*/ + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + sprintf(name, "ep%d%s-bulk", dev->ep_num, + ((desc->bEndpointAddress & USB_DIR_IN) ? "in":"out")); + break; + case USB_ENDPOINT_XFER_INT: + sprintf(name, "ep%d%s-intr", dev->ep_num, + ((desc->bEndpointAddress & USB_DIR_IN) ? "in":"out")); + break; + default: + sprintf(name, "ep%d%s", dev->ep_num, + ((desc->bEndpointAddress & USB_DIR_IN) ? "in":"out")); + break; + } + + virt_ep->desc = desc; + virt_ep->usb_ep.name = name; + virt_ep->usb_ep.ops = &pxa27x_ep_ops; + virt_ep->usb_ep.maxpacket = min((ushort)fifo_size, desc->wMaxPacketSize); + + list_add_tail(&virt_ep->usb_ep.ep_list, &gadget->ep_list); + + dev->ep_num++; + return &virt_ep->usb_ep; +} + +static int pxa27x_udc_get_frame(struct usb_gadget *_gadget) +{ + return (UDCFNR & 0x7FF); +} + +static int pxa27x_udc_wakeup(struct usb_gadget *_gadget) +{ + /* host may not have enabled remote wakeup */ + if ((UDCCR & UDCCR_DWRE) == 0) + return -EHOSTUNREACH; + udc_set_mask_UDCCR(UDCCR_UDR); + return 0; +} + +static const struct usb_gadget_ops pxa27x_udc_ops = { + .ep_alloc = pxa27x_ep_alloc, + .get_frame = pxa27x_udc_get_frame, + .wakeup = pxa27x_udc_wakeup, + // current versions must always be self-powered +}; + + +/*-------------------------------------------------------------------------*/ + +#ifdef UDC_PROC_FILE + +static const char proc_node_name [] = "driver/udc"; + +static int +udc_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + struct pxa27x_udc *dev = _dev; + char *next = buf; + unsigned size = count; + unsigned long flags; + int i, t; + u32 tmp; + + if (off != 0) + return 0; + + local_irq_save(flags); + + /* basic device status */ + t = scnprintf(next, size, DRIVER_DESC "\n" + "%s version: %s\nGadget driver: %s\n", + driver_name, DRIVER_VERSION DMASTR, + dev->driver ? dev->driver->driver.name : "(none)"); + size -= t; + next += t; + + /* registers for device and ep0 */ + t = scnprintf(next, size, + "uicr %02X.%02X, usir %02X.%02x, ufnr %02X\n", + UDCICR1, UDCICR0, UDCISR1, UDCISR0, UDCFNR); + size -= t; + next += t; + + tmp = UDCCR; + t = scnprintf(next, size,"udccr %02X =%s%s%s%s%s%s%s%s%s%s, con=%d,inter=%d,altinter=%d\n", tmp, + (tmp & UDCCR_OEN) ? " oen":"", + (tmp & UDCCR_AALTHNP) ? " aalthnp":"", + (tmp & UDCCR_AHNP) ? " rem" : "", + (tmp & UDCCR_BHNP) ? " rstir" : "", + (tmp & UDCCR_DWRE) ? " dwre" : "", + (tmp & UDCCR_SMAC) ? " smac" : "", + (tmp & UDCCR_EMCE) ? " emce" : "", + (tmp & UDCCR_UDR) ? " udr" : "", + (tmp & UDCCR_UDA) ? " uda" : "", + (tmp & UDCCR_UDE) ? " ude" : "", + (tmp & UDCCR_ACN) >> UDCCR_ACN_S, + (tmp & UDCCR_AIN) >> UDCCR_AIN_S, + (tmp & UDCCR_AAISN)>> UDCCR_AAISN_S ); + + size -= t; + next += t; + + tmp = UDCCSR0; + t = scnprintf(next, size, + "udccsr0 %02X =%s%s%s%s%s%s%s\n", tmp, + (tmp & UDCCSR0_SA) ? " sa" : "", + (tmp & UDCCSR0_RNE) ? " rne" : "", + (tmp & UDCCSR0_FST) ? " fst" : "", + (tmp & UDCCSR0_SST) ? " sst" : "", + (tmp & UDCCSR0_DME) ? " dme" : "", + (tmp & UDCCSR0_IPR) ? " ipr" : "", + (tmp & UDCCSR0_OPC) ? " opc" : ""); + size -= t; + next += t; + + if (!dev->driver) + goto done; + + t = scnprintf(next, size, "ep0 IN %lu/%lu, OUT %lu/%lu\nirqs %lu\n\n", + dev->stats.write.bytes, dev->stats.write.ops, + dev->stats.read.bytes, dev->stats.read.ops, + dev->stats.irqs); + size -= t; + next += t; + + /* dump endpoint queues */ + for (i = 0; i < UDC_EP_NUM; i++) { + struct pxa27x_ep *ep = &dev->ep [i]; + struct pxa27x_request *req; + int t; + + if (i != 0) { + const struct usb_endpoint_descriptor *d; + + d = ep->desc; + if (!d) + continue; + tmp = *dev->ep [i].reg_udccsr; + t = scnprintf(next, size, + "%d max %d %s udccs %02x udccr:0x%x\n", + i, le16_to_cpu (d->wMaxPacketSize), + (ep->dma >= 0) ? "dma" : "pio", tmp, + *dev->ep[i].reg_udccr); + /* TODO translate all five groups of udccs bits! */ + + } else /* ep0 should only have one transfer queued */ + t = scnprintf(next, size, "ep0 max 16 pio irqs %lu\n", + ep->pio_irqs); + if (t <= 0 || t > size) + goto done; + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, "\t(nothing queued)\n"); + if (t <= 0 || t > size) + goto done; + size -= t; + next += t; + continue; + } + list_for_each_entry(req, &ep->queue, queue) { +#ifdef USE_DMA + if (ep->dma >= 0 && req->queue.prev == &ep->queue) + t = scnprintf(next, size, "\treq %p len %d/%d " + "buf %p (dma%d dcmd %08x)\n", + &req->req, req->req.actual, + req->req.length, req->req.buf, + ep->dma, DCMD(ep->dma) + /* low 13 bits == bytes-to-go */); + else +#endif + t = scnprintf(next, size, + "\treq %p len %d/%d buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + if (t <= 0 || t > size) + goto done; + size -= t; + next += t; + } + } + +done: + local_irq_restore(flags); + *eof = 1; + return count - size; +} + +#define create_proc_files() \ + create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev) +#define remove_proc_files() \ + remove_proc_entry(proc_node_name, NULL) + +#else /* !UDC_PROC_FILE */ +#define create_proc_files() do {} while (0) +#define remove_proc_files() do {} while (0) + +#endif /* UDC_PROC_FILE */ + +/* "function" sysfs attribute */ +static ssize_t show_function(struct device *_dev, struct device_attribute *attr, char *buf) +{ + struct pxa27x_udc *dev = dev_get_drvdata(_dev); + + if (!dev->driver || !dev->driver->function + || strlen(dev->driver->function) > PAGE_SIZE) + return 0; + return scnprintf(buf, PAGE_SIZE, "%s\n", dev->driver->function); +} +static DEVICE_ATTR(function, S_IRUGO, show_function, NULL); + +/*-------------------------------------------------------------------------*/ + +/* + * udc_disable - disable USB device controller + */ +static void udc_disable(struct pxa27x_udc *dev) +{ + UDCICR0 = UDCICR1 = 0x00000000; + + udc_clear_mask_UDCCR(UDCCR_UDE); + + /* Disable clock for USB device */ + pxa_set_cken(CKEN_USB, 0); + + ep0_idle(dev); + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT); +} + + +/* + * udc_reinit - initialize software state + */ +static void udc_reinit(struct pxa27x_udc *dev) +{ + u32 i; + + dev->ep0state = EP0_IDLE; + + /* basic endpoint records init */ + for (i = 0; i < UDC_EP_NUM; i++) { + struct pxa27x_ep *ep = &dev->ep[i]; + + ep->stopped = 0; + ep->pio_irqs = ep->dma_irqs = 0; + } + dev->configuration = 0; + dev->interface = 0; + dev->alternate = 0; + /* the rest was statically initialized, and is read-only */ +} + +/* until it's enabled, this UDC should be completely invisible + * to any USB host. + */ +static void udc_enable(struct pxa27x_udc *dev) +{ + udc_clear_mask_UDCCR(UDCCR_UDE); + + /* Enable clock for USB device */ + pxa_set_cken(CKEN_USB, 1); + + UDCICR0 = UDCICR1 = 0; + + ep0_idle(dev); + dev->gadget.speed = USB_SPEED_FULL; + dev->stats.irqs = 0; + + udc_set_mask_UDCCR(UDCCR_UDE); + udelay(2); + if (UDCCR & UDCCR_EMCE) + dev_err(dev->dev, "There are error in configuration, udc disabled\n"); + + /* caller must be able to sleep in order to cope + * with startup transients. + */ + msleep(100); + + /* enable suspend/resume and reset irqs */ + UDCICR1 = UDCICR1_IECC | UDCICR1_IERU | UDCICR1_IESU | UDCICR1_IERS; + + /* enable ep0 irqs */ + UDCICR0 = UDCICR_INT(0,UDCICR_INT_MASK); + + DMSG("Connecting\n"); + /* RPFIXME */ + UP2OCR = UP2OCR_HXOE | UP2OCR_DPPUE | UP2OCR_DPPUBE; + //dev->mach->udc_command(PXA2XX_UDC_CMD_CONNECT); +} + + +/* when a driver is successfully registered, it will receive + * control requests including set_configuration(), which enables + * non-control requests. then usb traffic follows until a + * disconnect is reported. then a host may connect again, or + * the driver might get unbound. + */ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct pxa27x_udc *dev = the_controller; + int retval; + + if (!driver || driver->speed != USB_SPEED_FULL || !driver->bind + || !driver->unbind || !driver->disconnect || !driver->setup) + return -EINVAL; + if (!dev) + return -ENODEV; + if (dev->driver) + return -EBUSY; + + udc_disable(dev); + udc_init_ep(dev); + udc_reinit(dev); + + /* first hook up the driver ... */ + dev->driver = driver; + dev->gadget.dev.driver = &driver->driver; + dev->ep_num = 1; + + retval = device_add(&dev->gadget.dev); + if (retval) { + DMSG("device_add error %d\n", retval); + goto add_fail; + } + retval = driver->bind(&dev->gadget); + if (retval) { + DMSG("bind to driver %s --> error %d\n", + driver->driver.name, retval); + goto bind_fail; + } + retval = device_create_file(dev->dev, &dev_attr_function); + if (retval) { + DMSG("device_create_file failed: %d\n", retval); + goto create_file_fail; + } + + /* ... then enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + * NOTE: this shouldn't power up until later. + */ + DMSG("registered gadget driver '%s'\n", driver->driver.name); + udc_enable(dev); + dump_state(dev); + return 0; + +create_file_fail: + driver->unbind(&dev->gadget); +bind_fail: + device_del(&dev->gadget.dev); +add_fail: + dev->driver = 0; + dev->gadget.dev.driver = 0; + return retval; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +static void +stop_activity(struct pxa27x_udc *dev, struct usb_gadget_driver *driver) +{ + int i; + + DMSG("Trace path 1\n"); + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = 0; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < UDC_EP_NUM; i++) { + struct pxa27x_ep *ep = &dev->ep[i]; + + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + del_timer_sync(&dev->timer); + + /* report disconnect; the driver is already quiesced */ + if (driver) + driver->disconnect(&dev->gadget); + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct pxa27x_udc *dev = the_controller; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver) + return -EINVAL; + + local_irq_disable(); + udc_disable(dev); + stop_activity(dev, driver); + local_irq_enable(); + + driver->unbind(&dev->gadget); + pxa27x_ep_freeall(&dev->gadget); + dev->driver = 0; + + device_del(&dev->gadget.dev); + device_remove_file(dev->dev, &dev_attr_function); + + DMSG("unregistered gadget driver '%s'\n", driver->driver.name); + dump_state(dev); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + + +/*-------------------------------------------------------------------------*/ + +static inline void clear_ep_state(struct pxa27x_udc *dev) +{ + unsigned i; + + /* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint + * fifos, and pending transactions mustn't be continued in any case. + */ + for (i = 1; i < UDC_EP_NUM; i++) + nuke(&dev->ep[i], -ECONNABORTED); +} + +static void udc_watchdog(unsigned long _dev) +{ + struct pxa27x_udc *dev = (void *)_dev; + + local_irq_disable(); + if (dev->ep0state == EP0_STALL + && (UDCCSR0 & UDCCSR0_FST) == 0 + && (UDCCSR0 & UDCCSR0_SST) == 0) { + UDCCSR0 = UDCCSR0_FST|UDCCSR0_FTF; + DBG(DBG_VERBOSE, "ep0 re-stall\n"); + start_watchdog(dev); + } + local_irq_enable(); +} + +static void handle_ep0(struct pxa27x_udc *dev) +{ + u32 udccsr0 = UDCCSR0; + struct pxa27x_ep *ep = &dev->ep[0]; + struct pxa27x_request *req; + union { + struct usb_ctrlrequest r; + u8 raw[8]; + u32 word[2]; + } u; + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct pxa27x_request, queue); + + /* clear stall status */ + if (udccsr0 & UDCCSR0_SST) { + nuke(ep, -EPIPE); + UDCCSR0 = UDCCSR0_SST; + del_timer(&dev->timer); + ep0_idle(dev); + } + + /* previous request unfinished? non-error iff back-to-back ... */ + if ((udccsr0 & UDCCSR0_SA) != 0 && dev->ep0state != EP0_IDLE) { + nuke(ep, 0); + del_timer(&dev->timer); + ep0_idle(dev); + } + + switch (dev->ep0state) { + case EP0_NO_ACTION: + dev_info(dev->dev, "%s: Busy\n", __FUNCTION__); + /*Fall through */ + case EP0_IDLE: + /* late-breaking status? */ + udccsr0 = UDCCSR0; + + /* start control request? */ + if (likely((udccsr0 & (UDCCSR0_OPC|UDCCSR0_SA|UDCCSR0_RNE)) + == (UDCCSR0_OPC|UDCCSR0_SA|UDCCSR0_RNE))) { + int i; + + nuke(ep, -EPROTO); + /* read SETUP packet */ + for (i = 0; i < 2; i++) { + if (unlikely(!(UDCCSR0 & UDCCSR0_RNE))) { +bad_setup: + DMSG("SETUP %d!\n", i); + goto stall; + } + u.word [i] = UDCDR0; + } + if (unlikely((UDCCSR0 & UDCCSR0_RNE) != 0)) + goto bad_setup; + + le16_to_cpus(&u.r.wValue); + le16_to_cpus(&u.r.wIndex); + le16_to_cpus(&u.r.wLength); + + DBG(DBG_VERBOSE, "SETUP %02x.%02x v%04x i%04x l%04x\n", + u.r.bRequestType, u.r.bRequest, + u.r.wValue, u.r.wIndex, u.r.wLength); + /* cope with automagic for some standard requests. */ + dev->req_std = (u.r.bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD; + dev->req_config = 0; + dev->req_pending = 1; +#if 0 + switch (u.r.bRequest) { + /* hardware was supposed to hide this */ + case USB_REQ_SET_CONFIGURATION: + case USB_REQ_SET_INTERFACE: + case USB_REQ_SET_ADDRESS: + dev_err(dev->dev, "Should not come here\n"); + break; + } + +#endif + if (u.r.bRequestType & USB_DIR_IN) + dev->ep0state = EP0_IN_DATA_PHASE; + else + dev->ep0state = EP0_OUT_DATA_PHASE; + i = dev->driver->setup(&dev->gadget, &u.r); + + if (i < 0) { + /* hardware automagic preventing STALL... */ + if (dev->req_config) { + /* hardware sometimes neglects to tell + * tell us about config change events, + * so later ones may fail... + */ + WARN("config change %02x fail %d?\n", + u.r.bRequest, i); + return; + /* TODO experiment: if has_cfr, + * hardware didn't ACK; maybe we + * could actually STALL! + */ + } + DBG(DBG_VERBOSE, "protocol STALL, " + "%02x err %d\n", UDCCSR0, i); +stall: + /* the watchdog timer helps deal with cases + * where udc seems to clear FST wrongly, and + * then NAKs instead of STALLing. + */ + ep0start(dev, UDCCSR0_FST|UDCCSR0_FTF, "stall"); + start_watchdog(dev); + dev->ep0state = EP0_STALL; + + /* deferred i/o == no response yet */ + } else if (dev->req_pending) { + if (likely(dev->ep0state == EP0_IN_DATA_PHASE + || dev->req_std || u.r.wLength)) + ep0start(dev, 0, "defer"); + else + ep0start(dev, UDCCSR0_IPR, "defer/IPR"); + } + + /* expect at least one data or status stage irq */ + return; + + } else { + /* some random early IRQ: + * - we acked FST + * - IPR cleared + * - OPC got set, without SA (likely status stage) + */ + UDCCSR0 = udccsr0 & (UDCCSR0_SA|UDCCSR0_OPC); + } + break; + case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ + if (udccsr0 & UDCCSR0_OPC) { + UDCCSR0 = UDCCSR0_OPC|UDCCSR0_FTF; + DBG(DBG_VERBOSE, "ep0in premature status\n"); + if (req) + done(ep, req, 0); + ep0_idle(dev); + } else /* irq was IPR clearing */ { + if (req) { + /* this IN packet might finish the request */ + (void) write_ep0_fifo(ep, req); + } /* else IN token before response was written */ + } + break; + case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ + if (udccsr0 & UDCCSR0_OPC) { + if (req) { + /* this OUT packet might finish the request */ + if (read_ep0_fifo(ep, req)) + done(ep, req, 0); + /* else more OUT packets expected */ + } /* else OUT token before read was issued */ + } else /* irq was IPR clearing */ { + DBG(DBG_VERBOSE, "ep0out premature status\n"); + if (req) + done(ep, req, 0); + ep0_idle(dev); + } + break; + case EP0_STALL: + UDCCSR0 = UDCCSR0_FST; + break; + } + UDCISR0 = UDCISR_INT(0, UDCISR_INT_MASK); +} + + +static void handle_ep(struct pxa27x_ep *ep) +{ + struct pxa27x_request *req; + int completed; + u32 udccsr=0; + + DMSG("%s is called\n", __FUNCTION__); + do { + completed = 0; + if (likely (!list_empty(&ep->queue))) { + req = list_entry(ep->queue.next, + struct pxa27x_request, queue); + } else + req = 0; + +// udccsr = *ep->reg_udccsr; + DMSG("%s: req:%p, udcisr0:0x%x udccsr %p:0x%x\n", __FUNCTION__, + req, UDCISR0, ep->reg_udccsr, *ep->reg_udccsr); + if (unlikely(ep->dir_in)) { + udccsr = (UDCCSR_SST | UDCCSR_TRN) & *ep->reg_udccsr; + if (unlikely (udccsr)) + *ep->reg_udccsr = udccsr; + + if (req && likely ((*ep->reg_udccsr & UDCCSR_FS) != 0)) + completed = write_fifo(ep, req); + + } else { + udccsr = (UDCCSR_SST | UDCCSR_TRN) & *ep->reg_udccsr; + if (unlikely(udccsr)) + *ep->reg_udccsr = udccsr; + + /* fifos can hold packets, ready for reading... */ + if (likely(req)) { + completed = read_fifo(ep, req); + } else { + pio_irq_disable (ep->pxa_ep_num); + //*ep->reg_udccsr = UDCCSR_FEF; + DMSG("%s: no req for out data\n", + __FUNCTION__); + } + } + ep->pio_irqs++; + } while (completed); +} + +static void pxa27x_update_eps(struct pxa27x_udc *dev) +{ + struct pxa27x_virt_ep *virt_ep; + int i; + + for (i = 1; i < UDC_EP_NUM; i++) { + if(!dev->ep[i].assigned || !dev->ep[i].usb_ep) + continue; + virt_ep = container_of(dev->ep[i].usb_ep, struct pxa27x_virt_ep, usb_ep); + + DMSG("%s, Updating eps %d:%d, %d:%d, %d:%d, %p,%p\n", __FUNCTION__, dev->ep[i].config, dev->configuration + ,dev->ep[i].interface, dev->interface, dev->ep[i].aisn, dev->alternate, virt_ep->pxa_ep, &dev->ep[i]); + + if(dev->ep[i].config == dev->configuration && virt_ep->pxa_ep != &dev->ep[i]) { + if ((dev->ep[i].interface == dev->interface && + dev->ep[i].aisn == dev->alternate) || virt_ep->pxa_ep->config != dev->configuration) { + + if (virt_ep->pxa_ep->desc) { + DMSG("%s, Changing end point to %d (en/dis)\n", __FUNCTION__, i); + pxa27x_ep_disable(&virt_ep->usb_ep); + virt_ep->pxa_ep = &dev->ep[i]; + pxa27x_ep_enable(&virt_ep->usb_ep, virt_ep->desc); + } else { + DMSG("%s, Changing end point to %d (no en/dis)\n", __FUNCTION__, i); + virt_ep->pxa_ep = &dev->ep[i]; + } + } + } + } +} + +static void pxa27x_change_configuration(struct pxa27x_udc *dev) +{ + struct usb_ctrlrequest req ; + + pxa27x_update_eps(dev); + + req.bRequestType = 0; + req.bRequest = USB_REQ_SET_CONFIGURATION; + req.wValue = dev->configuration; + req.wIndex = 0; + req.wLength = 0; + + dev->ep0state = EP0_NO_ACTION; + dev->driver->setup(&dev->gadget, &req); +} + +static void pxa27x_change_interface(struct pxa27x_udc *dev) +{ + struct usb_ctrlrequest req; + + pxa27x_update_eps(dev); + + req.bRequestType = USB_RECIP_INTERFACE; + req.bRequest = USB_REQ_SET_INTERFACE; + req.wValue = dev->alternate; + req.wIndex = dev->interface; + req.wLength = 0; + + dev->ep0state = EP0_NO_ACTION; + dev->driver->setup(&dev->gadget, &req); +} + +/* + * pxa27x_udc_irq - interrupt handler + * + * avoid delays in ep0 processing. the control handshaking isn't always + * under software control (pxa250c0 and the pxa255 are better), and delays + * could cause usb protocol errors. + */ +static irqreturn_t pxa27x_udc_irq(int irq, void *_dev) +{ + struct pxa27x_udc *dev = _dev; + int handled; + + dev->stats.irqs++; + + DBG(DBG_VERBOSE, "Interrupt, UDCISR0:0x%08x, UDCISR1:0x%08x, " + "UDCCR:0x%08x\n", UDCISR0, UDCISR1, UDCCR); + do { + u32 udcir = UDCISR1 & 0xF8000000; + + handled = 0; + + /* SUSpend Interrupt Request */ + if (unlikely(udcir & UDCISR1_IRSU)) { + UDCISR1 = UDCISR1_IRSU; + handled = 1; + DBG(DBG_VERBOSE, "USB suspend\n"); + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + ep0_idle(dev); + } + + /* RESume Interrupt Request */ + if (unlikely(udcir & UDCISR1_IRRU)) { + UDCISR1 = UDCISR1_IRRU; + handled = 1; + DBG(DBG_VERBOSE, "USB resume\n"); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) + dev->driver->resume(&dev->gadget); + } + + if (unlikely(udcir & UDCISR1_IRCC)) { + unsigned config, interface, alternate; + + handled = 1; + DBG(DBG_VERBOSE, "USB SET_CONFIGURATION or " + "SET_INTERFACE command received\n"); + + config = (UDCCR & UDCCR_ACN) >> UDCCR_ACN_S; + + if (dev->configuration != config) { + dev->configuration = config; + pxa27x_change_configuration(dev) ; + } + + interface = (UDCCR & UDCCR_AIN) >> UDCCR_AIN_S; + alternate = (UDCCR & UDCCR_AAISN) >> UDCCR_AAISN_S; + + if ((dev->interface != interface) || (dev->alternate != alternate)) { + dev->interface = interface; + dev->alternate = alternate; + pxa27x_change_interface(dev); + } + + UDCCR |= UDCCR_SMAC; + + UDCISR1 = UDCISR1_IRCC; + DMSG("%s: con:%d,inter:%d,alt:%d\n", + __FUNCTION__, config,interface, alternate); + } + + /* ReSeT Interrupt Request - USB reset */ + if (unlikely(udcir & UDCISR1_IRRS)) { + UDCISR1 = UDCISR1_IRRS; + handled = 1; + + if ((UDCCR & UDCCR_UDA) == 0) { + DBG(DBG_VERBOSE, "USB reset start\n"); + + /* reset driver and endpoints, + * in case that's not yet done + */ + stop_activity(dev, dev->driver); + } + INFO("USB reset\n"); + dev->gadget.speed = USB_SPEED_FULL; + memset(&dev->stats, 0, sizeof dev->stats); + + } else { + u32 udcisr0 = UDCISR0 ; + u32 udcisr1 = UDCISR1 & 0xFFFF; + int i; + + if (unlikely (!udcisr0 && !udcisr1)) + continue; + + DBG(DBG_VERY_NOISY, "irq %02x.%02x\n", udcisr1,udcisr0); + + /* control traffic */ + if (udcisr0 & UDCISR0_IR0) { + dev->ep[0].pio_irqs++; + handle_ep0(dev); + handled = 1; + } + + udcisr0 >>= 2; + /* endpoint data transfers */ + for (i = 1; udcisr0!=0 && i < 16; udcisr0>>=2,i++) { + UDCISR0 = UDCISR_INT(i, UDCISR_INT_MASK); + + if (udcisr0 & UDC_INT_FIFOERROR) + dev_err(dev->dev, " Endpoint %d Fifo error\n", i); + if (udcisr0 & UDC_INT_PACKETCMP) { + handle_ep(&dev->ep[i]); + handled = 1; + } + + } + + for (i = 0; udcisr1!=0 && i < 8; udcisr1 >>= 2, i++) { + UDCISR1 = UDCISR_INT(i, UDCISR_INT_MASK); + + if (udcisr1 & UDC_INT_FIFOERROR) { + dev_err(dev->dev, "Endpoint %d fifo error\n", (i+16)); + } + + if (udcisr1 & UDC_INT_PACKETCMP) { + handle_ep(&dev->ep[i+16]); + handled = 1; + } + } + } + + /* we could also ask for 1 msec SOF (SIR) interrupts */ + + } while (handled); + return IRQ_HANDLED; +} + +int write_ep0_zlp(void) +{ + UDCCSR0 = UDCCSR0_IPR; + return 0; +} +EXPORT_SYMBOL(write_ep0_zlp); + +static void udc_init_ep(struct pxa27x_udc *dev) +{ + int i; + + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + + for (i = 0; i < UDC_EP_NUM; i++) { + struct pxa27x_ep *ep = &dev->ep[i]; + + ep->dma = -1; + if (i != 0) { + memset(ep, 0, sizeof(*ep)); + } + INIT_LIST_HEAD(&ep->queue); + } +} + +/*-------------------------------------------------------------------------*/ + +static void nop_release(struct device *dev) +{ + DMSG("%s %s\n", __FUNCTION__, dev->bus_id); +} + +/* this uses load-time allocation and initialization (instead of + * doing it at run-time) to save code, eliminate fault paths, and + * be more obviously correct. + */ + +static struct pxa27x_udc memory = { + .gadget = { + .ops = &pxa27x_udc_ops, + .ep0 = &memory.virt_ep0.usb_ep, + .name = driver_name, + .dev = { + .bus_id = "gadget", + .release = nop_release, + }, + }, + + /* control endpoint */ + .virt_ep0 = { + .pxa_ep = &memory.ep[0], + .usb_ep = { + .name = ep0name, + .ops = &pxa27x_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + }, + + .ep[0] = { + .usb_ep = &memory.virt_ep0.usb_ep, + .dev = &memory, + .reg_udccsr = &UDCCSR0, + .reg_udcdr = &UDCDR0, + }, +}; + +static int __init pxa27x_udc_probe(struct platform_device *_dev) +{ + struct pxa27x_udc *dev = &memory; + int retval; + + /* other non-static parts of init */ + dev->dev = &_dev->dev; + dev->mach = _dev->dev.platform_data; + + /* RPFIXME */ + UP2OCR = UP2OCR_HXOE | UP2OCR_DPPUE | UP2OCR_DPPUBE; + + init_timer(&dev->timer); + dev->timer.function = udc_watchdog; + dev->timer.data = (unsigned long) dev; + + device_initialize(&dev->gadget.dev); + dev->gadget.dev.parent = &_dev->dev; + dev->gadget.dev.dma_mask = _dev->dev.dma_mask; + + the_controller = dev; + platform_set_drvdata(_dev, dev); + + udc_disable(dev); + udc_init_ep(dev); + udc_reinit(dev); + + /* irq setup after old hardware state is cleaned up */ + retval = request_irq(IRQ_USB, pxa27x_udc_irq, + SA_INTERRUPT, driver_name, dev); + if (retval != 0) { + dev_err(dev->dev, "%s: can't get irq %i, err %d\n", + driver_name, IRQ_USB, retval); + return -EBUSY; + } + dev->got_irq = 1; + + create_proc_files(); + + return 0; +} + +static int pxa27x_udc_remove(struct platform_device *_dev) +{ + struct pxa27x_udc *dev = platform_get_drvdata(_dev); + + udc_disable(dev); + remove_proc_files(); + usb_gadget_unregister_driver(dev->driver); + + pxa27x_ep_freeall(&dev->gadget); + + if (dev->got_irq) { + free_irq(IRQ_USB, dev); + dev->got_irq = 0; + } + platform_set_drvdata(_dev, 0); + the_controller = 0; + return 0; +} + +#ifdef CONFIG_PM +static void pxa27x_udc_shutdown(struct platform_device *_dev) +{ + struct pxa27x_udc *dev = platform_get_drvdata(_dev); + + udc_disable(dev); +} + +static int pxa27x_udc_suspend(struct platform_device *_dev, pm_message_t state) +{ + int i; + struct pxa27x_udc *dev = platform_get_drvdata(_dev); + + DMSG("%s is called\n", __FUNCTION__); + + dev->udccsr0 = UDCCSR0; + for(i=1; (i<UDC_EP_NUM); i++) { + if (dev->ep[i].assigned) { + struct pxa27x_ep *ep = &dev->ep[i]; + ep->udccsr_value = *ep->reg_udccsr; + ep->udccr_value = *ep->reg_udccr; + DMSG("EP%d, udccsr:0x%x, udccr:0x%x\n", + i, *ep->reg_udccsr, *ep->reg_udccr); + } + } + + udc_clear_mask_UDCCR(UDCCR_UDE); + pxa_set_cken(CKEN_USB, 0); + + return 0; +} + +static int pxa27x_udc_resume(struct platform_device *_dev) +{ + int i; + struct pxa27x_udc *dev = platform_get_drvdata(_dev); + + DMSG("%s is called\n", __FUNCTION__); + UDCCSR0 = dev->udccsr0 & (UDCCSR0_FST | UDCCSR0_DME); + for (i=1; i < UDC_EP_NUM; i++) { + if (dev->ep[i].assigned) { + struct pxa27x_ep *ep = &dev->ep[i]; + *ep->reg_udccsr = ep->udccsr_value; + *ep->reg_udccr = ep->udccr_value; + DMSG("EP%d, udccsr:0x%x, udccr:0x%x\n", + i, *ep->reg_udccsr, *ep->reg_udccr); + } + } + + udc_enable(dev); + + /* OTGPH bit is set when sleep mode is entered. + * it indicates that OTG pad is retaining its state. + * Upon exit from sleep mode and before clearing OTGPH, + * Software must configure the USB OTG pad, UDC, and UHC + * to the state they were in before entering sleep mode.*/ + PSSR |= PSSR_OTGPH; + + return 0; +} +#endif + +/*-------------------------------------------------------------------------*/ + +static struct platform_driver udc_driver = { + .driver = { + .name = "pxa2xx-udc", + }, + .probe = pxa27x_udc_probe, + .remove = pxa27x_udc_remove, +#ifdef CONFIG_PM + .shutdown = pxa27x_udc_shutdown, + .suspend = pxa27x_udc_suspend, + .resume = pxa27x_udc_resume +#endif +}; + +static int __init udc_init(void) +{ + printk(KERN_INFO "%s: version %s\n", driver_name, DRIVER_VERSION); + return platform_driver_register(&udc_driver); +} +module_init(udc_init); + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&udc_driver); +} +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Frank Becker, Robert Schwebel, David Brownell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/pxa27x_udc.h b/drivers/usb/gadget/pxa27x_udc.h new file mode 100644 index 0000000..d4377cf --- /dev/null +++ b/drivers/usb/gadget/pxa27x_udc.h @@ -0,0 +1,298 @@ +/* + * linux/drivers/usb/gadget/pxa27x_udc.h + * Intel PXA27x on-chip full speed USB device controller + * + * Copyright (C) 2003 Robert Schwebel <r.schwebel@pengutronix.de>, Pengutronix + * Copyright (C) 2003 David Brownell + * Copyright (C) 2004 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __LINUX_USB_GADGET_PXA27X_H +#define __LINUX_USB_GADGET_PXA27X_H + +#include <linux/types.h> + +struct pxa27x_udc; + +struct pxa27x_ep { + struct pxa27x_udc *dev; + struct usb_ep *usb_ep; + const struct usb_endpoint_descriptor *desc; + + struct list_head queue; + unsigned long pio_irqs; + unsigned long dma_irqs; + + unsigned pxa_ep_num; + int dma; + unsigned fifo_size; + unsigned ep_type; + + unsigned stopped : 1; + unsigned dma_con : 1; + unsigned dir_in : 1; + unsigned assigned : 1; + + unsigned ep_num; + unsigned config; + unsigned interface; + unsigned aisn; + /* UDCCSR = UDC Control/Status Register for this EP + * UBCR = UDC Byte Count Remaining (contents of OUT fifo) + * UDCDR = UDC Endpoint Data Register (the fifo) + * UDCCR = UDC Endpoint Configuration Registers + * DRCM = DMA Request Channel Map + */ + volatile u32 *reg_udccsr; + volatile u32 *reg_udcbcr; + volatile u32 *reg_udcdr; + volatile u32 *reg_udccr; +#ifdef USE_DMA + volatile u32 *reg_drcmr; +#define drcmr(n) .reg_drcmr = & DRCMR ## n , +#else +#define drcmr(n) +#endif + +#ifdef CONFIG_PM + unsigned udccsr_value; + unsigned udccr_value; +#endif +}; + +struct pxa27x_virt_ep { + struct usb_ep usb_ep; + const struct usb_endpoint_descriptor *desc; + struct pxa27x_ep *pxa_ep; +}; + +struct pxa27x_request { + struct usb_request req; + struct list_head queue; +}; + +enum ep0_state { + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, +// EP0_END_XFER, + EP0_STALL, + EP0_NO_ACTION +}; + +#define EP0_FIFO_SIZE ((unsigned)16) +#define BULK_FIFO_SIZE ((unsigned)64) +#define ISO_FIFO_SIZE ((unsigned)256) +#define INT_FIFO_SIZE ((unsigned)8) + +struct udc_stats { + struct ep0stats { + unsigned long ops; + unsigned long bytes; + } read, write; + unsigned long irqs; +}; + +#define UDC_EP_NUM 24 + + +struct pxa27x_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + enum ep0_state ep0state; + struct udc_stats stats; + unsigned got_irq : 1, + has_cfr : 1, + req_pending : 1, + req_std : 1, + req_config : 1; + +#define start_watchdog(dev) mod_timer(&dev->timer, jiffies + (HZ/200)) + struct timer_list timer; + + struct device *dev; + struct pxa2xx_udc_mach_info *mach; + u64 dma_mask; + struct pxa27x_virt_ep virt_ep0; + struct pxa27x_ep ep[UDC_EP_NUM]; + unsigned int ep_num; + + unsigned configuration, + interface, + alternate; +#ifdef CONFIG_PM + unsigned udccsr0; +#endif +}; + +static struct pxa27x_udc *the_controller; + +#if 0 +/*-------------------------------------------------------------------------*/ + + +/* one GPIO should be used to detect host disconnect */ +static inline int is_usb_connected(void) +{ + if (!the_controller->mach->udc_is_connected) + return 1; + return the_controller->mach->udc_is_connected(); +} + +/* one GPIO should force the host to see this device (or not) */ +static inline void make_usb_disappear(void) +{ + if (!the_controller->mach->udc_command) + return; + the_controller->mach->udc_command(PXA27X_UDC_CMD_DISCONNECT); +} + +static inline void let_usb_appear(void) +{ + if (!the_controller->mach->udc_command) + return; + the_controller->mach->udc_command(PXA2XX_UDC_CMD_CONNECT); +} +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * Debugging support vanishes in non-debug builds. DBG_NORMAL should be + * mostly silent during normal use/testing, with no timing side-effects. + */ +#define DBG_NORMAL 1 /* error paths, device state transitions */ +#define DBG_VERBOSE 2 /* add some success path trace info */ +#define DBG_NOISY 3 /* ... even more: request level */ +#define DBG_VERY_NOISY 4 /* ... even more: packet level */ + +#ifdef DEBUG +static const char *state_name[] = { + "EP0_IDLE", + "EP0_IN_DATA_PHASE", "EP0_OUT_DATA_PHASE", + "EP0_END_XFER", "EP0_STALL" +}; + +#define DMSG(stuff...) printk(KERN_ERR "udc: " stuff) + +#ifdef VERBOSE +# define UDC_DEBUG DBG_VERBOSE +#else +# define UDC_DEBUG DBG_NORMAL +#endif + +static void __attribute__ ((__unused__)) +dump_udccr(const char *label) +{ + u32 udccr = UDCCR; + DMSG("%s 0x%08x =%s%s%s%s%s%s%s%s%s%s, con=%d,inter=%d,altinter=%d\n", + label, udccr, + (udccr & UDCCR_OEN) ? " oen":"", + (udccr & UDCCR_AALTHNP) ? " aalthnp":"", + (udccr & UDCCR_AHNP) ? " rem" : "", + (udccr & UDCCR_BHNP) ? " rstir" : "", + (udccr & UDCCR_DWRE) ? " dwre" : "", + (udccr & UDCCR_SMAC) ? " smac" : "", + (udccr & UDCCR_EMCE) ? " emce" : "", + (udccr & UDCCR_UDR) ? " udr" : "", + (udccr & UDCCR_UDA) ? " uda" : "", + (udccr & UDCCR_UDE) ? " ude" : "", + (udccr & UDCCR_ACN) >> UDCCR_ACN_S, + (udccr & UDCCR_AIN) >> UDCCR_AIN_S, + (udccr & UDCCR_AAISN)>> UDCCR_AAISN_S ); +} + +static void __attribute__ ((__unused__)) +dump_udccsr0(const char *label) +{ + u32 udccsr0 = UDCCSR0; + + DMSG("%s %s 0x%08x =%s%s%s%s%s%s%s\n", + label, state_name[the_controller->ep0state], udccsr0, + (udccsr0 & UDCCSR0_SA) ? " sa" : "", + (udccsr0 & UDCCSR0_RNE) ? " rne" : "", + (udccsr0 & UDCCSR0_FST) ? " fst" : "", + (udccsr0 & UDCCSR0_SST) ? " sst" : "", + (udccsr0 & UDCCSR0_DME) ? " dme" : "", + (udccsr0 & UDCCSR0_IPR) ? " ipr" : "", + (udccsr0 & UDCCSR0_OPC) ? " opr" : ""); +} + +static void __attribute__ ((__unused__)) +dump_state(struct pxa27x_udc *dev) +{ + unsigned i; + + DMSG("%s, udcicr %02X.%02X, udcsir %02X.%02x, udcfnr %02X\n", + state_name[dev->ep0state], + UDCICR1, UDCICR0, UDCISR1, UDCISR0, UDCFNR); + dump_udccr("udccr"); + + if (!dev->driver) { + DMSG("no gadget driver bound\n"); + return; + } else + DMSG("ep0 driver '%s'\n", dev->driver->driver.name); + + + dump_udccsr0 ("udccsr0"); + DMSG("ep0 IN %lu/%lu, OUT %lu/%lu\n", + dev->stats.write.bytes, dev->stats.write.ops, + dev->stats.read.bytes, dev->stats.read.ops); + + for (i = 1; i < UDC_EP_NUM; i++) { + if (dev->ep[i].assigned) + DMSG ("udccs%d = %02x\n", i, *dev->ep->reg_udccsr); + } +} + +#if 0 +static void dump_regs(u8 ep) +{ + DMSG("EP:%d UDCCSR:0x%08x UDCBCR:0x%08x\n UDCCR:0x%08x\n", + ep,UDCCSN(ep), UDCBCN(ep), UDCCN(ep)); +} +static void dump_req (struct pxa27x_request *req) +{ + struct usb_request *r = &req->req; + + DMSG("%s: buf:0x%08x length:%d dma:0x%08x actual:%d\n", + __FUNCTION__, (unsigned)r->buf, r->length, + r->dma, r->actual); +} +#endif + +#else + +#define DMSG(stuff...) do{}while(0) + +#define dump_udccr(x) do{}while(0) +#define dump_udccsr0(x) do{}while(0) +#define dump_state(x) do{}while(0) + +#define UDC_DEBUG ((unsigned)4) + +#endif + +#define DBG(lvl, stuff...) do{if ((lvl) <= UDC_DEBUG) DMSG(stuff);}while(0) + +#define WARN(stuff...) printk(KERN_WARNING "udc: " stuff) +#define INFO(stuff...) printk(KERN_INFO "udc: " stuff) + + +#endif /* __LINUX_USB_GADGET_PXA27X_H */ diff --git a/drivers/usb/gadget/pxa2xx_udc.h b/drivers/usb/gadget/pxa2xx_udc.h index 0e5d0e6..9483a49 100644 --- a/drivers/usb/gadget/pxa2xx_udc.h +++ b/drivers/usb/gadget/pxa2xx_udc.h @@ -206,7 +206,8 @@ dump_state(struct pxa2xx_udc *dev) unsigned i; DMSG("%s %s, uicr %02X.%02X, usir %02X.%02x, ufnr %02X.%02X\n", - is_usb_connected() ? "host " : "disconnected", + //is_usb_connected() ? "host " : "disconnected", + "host ", state_name[dev->ep0state], UICR1, UICR0, USIR1, USIR0, UFNRH, UFNRL); dump_udccr("udccr"); @@ -223,8 +224,8 @@ dump_state(struct pxa2xx_udc *dev) } else DMSG("ep0 driver '%s'\n", dev->driver->driver.name); - if (!is_usb_connected()) - return; + //if (!is_usb_connected()) + // return; dump_udccs0 ("udccs0"); DMSG("ep0 IN %lu/%lu, OUT %lu/%lu\n", diff --git a/drivers/usb/gadget/serial.c b/drivers/usb/gadget/serial.c index ce4d2e0..5dac23f 100644 --- a/drivers/usb/gadget/serial.c +++ b/drivers/usb/gadget/serial.c @@ -1356,6 +1356,7 @@ static int __init gs_bind(struct usb_gadget *gadget) struct usb_ep *ep; struct gs_dev *dev; int gcnum; + struct usb_endpoint_config ep_config[2]; /* Some controllers can't support CDC ACM: * - sh doesn't support multiple interfaces or configs; @@ -1376,22 +1377,33 @@ static int __init gs_bind(struct usb_gadget *gadget) __constant_cpu_to_le16(GS_VERSION_NUM|0x0099); } + ep_config[0].config = GS_BULK_CONFIG_ID; + ep_config[0].interface = gs_bulk_interface_desc.bInterfaceNumber; + ep_config[0].altinterface = gs_bulk_interface_desc.bAlternateSetting; + ep_config[1].config = GS_ACM_CONFIG_ID; + ep_config[1].interface = gs_data_interface_desc.bInterfaceNumber; + ep_config[1].altinterface = gs_data_interface_desc.bAlternateSetting; + usb_ep_autoconfig_reset(gadget); - ep = usb_ep_autoconfig(gadget, &gs_fullspeed_in_desc); + ep = usb_ep_autoconfig(gadget, &gs_fullspeed_in_desc, &ep_config[0], 2); if (!ep) goto autoconf_fail; EP_IN_NAME = ep->name; ep->driver_data = ep; /* claim the endpoint */ - ep = usb_ep_autoconfig(gadget, &gs_fullspeed_out_desc); + ep = usb_ep_autoconfig(gadget, &gs_fullspeed_out_desc, &ep_config[0], 2); if (!ep) goto autoconf_fail; EP_OUT_NAME = ep->name; ep->driver_data = ep; /* claim the endpoint */ if (use_acm) { - ep = usb_ep_autoconfig(gadget, &gs_fullspeed_notify_desc); + ep_config[0].config = GS_ACM_CONFIG_ID; + ep_config[0].interface = gs_control_interface_desc.bInterfaceNumber; + ep_config[0].altinterface = gs_control_interface_desc.bAlternateSetting; + + ep = usb_ep_autoconfig(gadget, &gs_fullspeed_notify_desc, &ep_config[0], 1); if (!ep) { printk(KERN_ERR "gs_bind: cannot run ACM on %s\n", gadget->name); goto autoconf_fail; diff --git a/drivers/usb/gadget/zero.c b/drivers/usb/gadget/zero.c index fcfe869..1e40d46 100644 --- a/drivers/usb/gadget/zero.c +++ b/drivers/usb/gadget/zero.c @@ -1142,6 +1142,7 @@ zero_bind (struct usb_gadget *gadget) struct zero_dev *dev; struct usb_ep *ep; int gcnum; + struct usb_endpoint_config ep_config[2]; /* FIXME this can't yet work right with SH ... it has only * one configuration, numbered one. @@ -1154,7 +1155,15 @@ zero_bind (struct usb_gadget *gadget) * but there may also be important quirks to address. */ usb_ep_autoconfig_reset (gadget); - ep = usb_ep_autoconfig (gadget, &fs_source_desc); + + ep_config[0].config = CONFIG_SOURCE_SINK; + ep_config[0].interface = source_sink_intf.bInterfaceNumber; + ep_config[0].altinterface = source_sink_intf.bAlternateSetting; + ep_config[1].config = CONFIG_LOOPBACK; + ep_config[1].interface = loopback_intf.bInterfaceNumber; + ep_config[1].altinterface = loopback_intf.bAlternateSetting; + + ep = usb_ep_autoconfig(gadget, &fs_source_desc, &ep_config[0], 2); if (!ep) { autoconf_fail: printk (KERN_ERR "%s: can't autoconfigure on %s\n", @@ -1164,7 +1173,7 @@ autoconf_fail: EP_IN_NAME = ep->name; ep->driver_data = ep; /* claim */ - ep = usb_ep_autoconfig (gadget, &fs_sink_desc); + ep = usb_ep_autoconfig(gadget, &fs_sink_desc, &ep_config[0], 2); if (!ep) goto autoconf_fail; EP_OUT_NAME = ep->name; diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 2580f5f..12e4b91 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -40,7 +40,7 @@ config BACKLIGHT_CLASS_DEVICE config BACKLIGHT_CORGI tristate "Sharp Corgi Backlight Driver (SL Series)" - depends on BACKLIGHT_CLASS_DEVICE && PXA_SHARPSL + depends on BACKLIGHT_CLASS_DEVICE && (PXA_SHARPSL || MACH_EM_X270) default y help If you have a Sharp Zaurus SL-C7xx, SL-Cxx00 or SL-6000x say y to enable the diff --git a/include/asm-arm/arch-pxa/pwr-i2c.h b/include/asm-arm/arch-pxa/pwr-i2c.h new file mode 100644 index 0000000..6bad486 --- /dev/null +++ b/include/asm-arm/arch-pxa/pwr-i2c.h @@ -0,0 +1,61 @@ +/* + * (C) Copyright 2007 CompuLab, Ltd + * Mike Rapoport <mike@compulab.co.il> + * + * Simple Power I2C interface for PXA processors. + * Based on U-Boot PXA I2C driver. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ + +#ifndef __PXA_PWR_I2C_H__ +#define __PXA_PWR_I2C_H__ + +/* + * Configuration items. + */ +#define I2C_RXTX_LEN 128 /* maximum tx/rx buffer length */ + +/* + * Probe the given I2C chip address. Returns 0 if a chip responded, + * not 0 on failure. + */ +extern int pxa_pwr_i2c_probe(u8 chip); + +/* + * Read/Write interface: + * chip: I2C chip address, range 0..127 + * addr: Memory (register) address within the chip + * alen: Number of bytes to use for addr (typically 1, 2 for larger + * memories, 0 for register type devices with only one + * register) + * buffer: Where to read/write the data + * len: How many bytes to read/write + * + * Returns: 0 on success, not 0 on failure + */ +extern int pxa_pwr_i2c_read(u8 chip, uint addr, int alen, u8 *buffer, int len); +extern int pxa_pwr_i2c_write(u8 chip, uint addr, int alen, + u8 *buffer, int len); + +/* + * Utility routines to read/write registers. + */ +extern s32 pxa_pwr_i2c_reg_read (u8 chip, u8 reg); +extern s32 pxa_pwr_i2c_reg_write(u8 chip, u8 reg, u8 val); + +#endif /* __PXA_PWR_I2C_H___ */ diff --git a/include/linux/da9030.h b/include/linux/da9030.h new file mode 100644 index 0000000..6eb89e2 --- /dev/null +++ b/include/linux/da9030.h @@ -0,0 +1,118 @@ +#ifndef _DA9030_H +#define _DA9030_H + +/* DA9030 has 24 possible interrupts */ +#define DA9030_IRQ_COUNT 24 + +/* EVENT A */ +#define DA9030_IRQ_ONKEY_EN 0 +#define DA9030_IRQ_PWREN1 1 +#define DA9030_IRQ_EXTON 2 +#define DA9030_IRQ_CHDET 3 +#define DA9030_IRQ_TBAT 4 +#define DA9030_IRQ_VBATMON 5 +#define DA9030_IRQ_VBATMONTXON 6 +#define DA9030_IRQ_CHIOVER 7 + +/* EVENT B */ +#define DA9030_IRQ_TCTO 8 +#define DA9030_IRQ_CCTO 9 +#define DA9030_IRQ_ADC_READY 10 +#define DA9030_IRQ_VBUS_4_4 11 +#define DA9030_IRQ_VBUS_4_0 12 +#define DA9030_IRQ_SESSION_VALID 13 +#define DA9030_IRQ_SRP_DETECT 14 +#define DA9030_IRQ_WDOG_INTR 15 + +/* EVENT C */ +#define DA9030_IRQ_LDO15 16 +#define DA9030_IRQ_LDO16 17 +#define DA9030_IRQ_LDO17 18 +#define DA9030_IRQ_LDO18 19 +#define DA9030_IRQ_LDO19 20 +#define DA9030_IRQ_BUCK2 21 +#define DA9030_IRQ_ADC_IN4 22 +#define DA9030_IRQ_ADC_IN5 23 + +enum da9030_ldo_sleep_mode { + DA9030_LDO_SLEEP_KEEP = 0x0, + DA9030_LDO_SLEEP_SHUT_DOWN = 0x1, + DA9030_LDO_SLEEP_POWER_UP = 0x2, + DA9030_LDO_SLEEP_HIGH_Z = 0x3, +}; + +enum da9030_led_rate { + DA9030_LED_RATE_ON = 0x0, + DA9030_LED_RATE_0_52 = 0x1, + DA9030_LED_RATE_1_05 = 0x2, + DA9030_LED_RATE_2_1 = 0x3, +}; + +enum da9030_led_duty_cycle { + DA9030_LED_DUTY_1_16 = 0x0, + DA9030_LED_DUTY_1_8 = 0x1, + DA9030_LED_DUTY_1_4 = 0x2, + DA9030_LED_DUTY_1_2 = 0x3, +}; + +enum da9030_led_pwm_chop { + DA9030_LED_PWM_8_8 = 0x0, + DA9030_LED_PWM_7_8 = 0x1, + DA9030_LED_PWM_6_8 = 0x2, + DA9030_LED_PWM_5_8 = 0x3, + DA9030_LED_PWM_4_8 = 0x4, + DA9030_LED_PWM_3_8 = 0x5, + DA9030_LED_PWM_2_8 = 0x6, + DA9030_LED_PWM_1_8 = 0x7, +}; + +struct da9030_adc_res { + int vbat_res; + int vbatmin_res; + int vbatmintxon; + int ichmax_res; + int ichmin_res; + int ichaverage_res; + int vchmax_res; + int vchmin_res; + int tbat_res; + int adc_in4_res; + int adc_in5_res; +}; + +extern int da9030_get_status(void); +extern int da9030_get_fault_log(void); + +extern void da9030_enable_adc(void); +extern void da9030_read_adc(struct da9030_adc_res *adc); + +extern void da9030_set_wled(int on, unsigned int brightness); + +extern int da9030_set_led(int led, int on, + enum da9030_led_rate rate, + enum da9030_led_duty_cycle duty, + enum da9030_led_pwm_chop pwm_chop); + +extern int da9030_set_charger(int on, unsigned int mA, unsigned int mV); +extern void da9030_get_charger(int *on, unsigned int *mA, unsigned int *mV); + +extern int da9030_set_ldo(int ldo, int on, unsigned int mV, + enum da9030_ldo_sleep_mode sleep_mode); +extern int da9030_set_buck(int buck, int on, unsigned int mV, int flags); + +extern void da9030_set_thresholds(unsigned int tbathighp, + unsigned int tbathighn, + unsigned int tbatlow, + unsigned int vbatmon); + +extern int da9030_register_callback(int event, + void (*callback)(int event, void *data), + void *data); +extern void da9030_unregister_callback(int event); + +/* Keep these for now, although they are unsafe. When I'll see what + other methods are needed from DA9030, I'll remove these */ +extern s32 da9030_get_reg(u32 reg); +extern s32 da9030_set_reg(u32 reg, u8 val); + +#endif diff --git a/include/linux/usb_gadget.h b/include/linux/usb_gadget.h index 4f59b2a..792c568 100644 --- a/include/linux/usb_gadget.h +++ b/include/linux/usb_gadget.h @@ -397,10 +397,28 @@ usb_ep_fifo_flush (struct usb_ep *ep) struct usb_gadget; +/** + * struct usb_endpoint_config - possible configurations of a given endpoint + * @config: the configuration number + * @interface: the interface number + * @altinterface: the altinterface number + * + * Used as an array to pass information about the possible configurations + * of a given endpoint to the bus controller. + */ +struct usb_endpoint_config { + u8 config; + u8 interface; + u8 altinterface; +}; + /* the rest of the api to the controller hardware: device operations, * which don't involve endpoints (or i/o). */ struct usb_gadget_ops { + struct usb_ep* (*ep_alloc)(struct usb_gadget *, + struct usb_endpoint_descriptor *, + struct usb_endpoint_config *, int); int (*get_frame)(struct usb_gadget *); int (*wakeup)(struct usb_gadget *); int (*set_selfpowered) (struct usb_gadget *, int is_selfpowered); @@ -824,7 +842,10 @@ int usb_gadget_config_buf(const struct usb_config_descriptor *config, /* utility wrapping a simple endpoint selection policy */ extern struct usb_ep *usb_ep_autoconfig (struct usb_gadget *, - struct usb_endpoint_descriptor *) __devinit; + struct usb_endpoint_descriptor *, + struct usb_endpoint_config *, + int numconfigs +); extern void usb_ep_autoconfig_reset (struct usb_gadget *) __devinit; diff --git a/include/linux/wm97xx.h b/include/linux/wm97xx.h new file mode 100644 index 0000000..354e533 --- /dev/null +++ b/include/linux/wm97xx.h @@ -0,0 +1,291 @@ + +/* + * Register bits and API for Wolfson WM97xx series of codecs + */ + +#ifndef _LINUX_WM97XX_H +#define _LINUX_WM97XX_H + +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/input.h> /* Input device layer */ + +/* + * WM97xx AC97 Touchscreen registers + */ +#define AC97_WM97XX_DIGITISER1 0x76 +#define AC97_WM97XX_DIGITISER2 0x78 +#define AC97_WM97XX_DIGITISER_RD 0x7a +#define AC97_WM9713_DIG1 0x74 +#define AC97_WM9713_DIG2 AC97_WM97XX_DIGITISER1 +#define AC97_WM9713_DIG3 AC97_WM97XX_DIGITISER2 + +/* + * WM97xx register bits + */ +#define WM97XX_POLL 0x8000 /* initiate a polling measurement */ +#define WM97XX_ADCSEL_X 0x1000 /* x coord measurement */ +#define WM97XX_ADCSEL_Y 0x2000 /* y coord measurement */ +#define WM97XX_ADCSEL_PRES 0x3000 /* pressure measurement */ +#define WM97XX_ADCSEL_MASK 0x7000 +#define WM97XX_COO 0x0800 /* enable coordinate mode */ +#define WM97XX_CTC 0x0400 /* enable continuous mode */ +#define WM97XX_CM_RATE_93 0x0000 /* 93.75Hz continuous rate */ +#define WM97XX_CM_RATE_187 0x0100 /* 187.5Hz continuous rate */ +#define WM97XX_CM_RATE_375 0x0200 /* 375Hz continuous rate */ +#define WM97XX_CM_RATE_750 0x0300 /* 750Hz continuous rate */ +#define WM97XX_CM_RATE_8K 0x00f0 /* 8kHz continuous rate */ +#define WM97XX_CM_RATE_12K 0x01f0 /* 12kHz continuous rate */ +#define WM97XX_CM_RATE_24K 0x02f0 /* 24kHz continuous rate */ +#define WM97XX_CM_RATE_48K 0x03f0 /* 48kHz continuous rate */ +#define WM97XX_CM_RATE_MASK 0x03f0 +#define WM97XX_RATE(i) (((i & 3) << 8) | ((i & 4) ? 0xf0 : 0)) +#define WM97XX_DELAY(i) ((i << 4) & 0x00f0) /* sample delay times */ +#define WM97XX_DELAY_MASK 0x00f0 +#define WM97XX_SLEN 0x0008 /* slot read back enable */ +#define WM97XX_SLT(i) ((i - 5) & 0x7) /* touchpanel slot selection (5-11) */ +#define WM97XX_SLT_MASK 0x0007 +#define WM97XX_PRP_DETW 0x4000 /* pen detect on, digitiser off, wake up */ +#define WM97XX_PRP_DET 0x8000 /* pen detect on, digitiser off, no wake up */ +#define WM97XX_PRP_DET_DIG 0xc000 /* pen detect on, digitiser on */ +#define WM97XX_RPR 0x2000 /* wake up on pen down */ +#define WM97XX_PEN_DOWN 0x8000 /* pen is down */ +#define WM97XX_ADCSRC_MASK 0x7000 /* ADC source mask */ + +#define WM97XX_AUX_ID1 0x8001 +#define WM97XX_AUX_ID2 0x8002 +#define WM97XX_AUX_ID3 0x8003 +#define WM97XX_AUX_ID4 0x8004 + + +/* WM9712 Bits */ +#define WM9712_45W 0x1000 /* set for 5-wire touchscreen */ +#define WM9712_PDEN 0x0800 /* measure only when pen down */ +#define WM9712_WAIT 0x0200 /* wait until adc is read before next sample */ +#define WM9712_PIL 0x0100 /* current used for pressure measurement. set 400uA else 200uA */ +#define WM9712_MASK_HI 0x0040 /* hi on mask pin (47) stops conversions */ +#define WM9712_MASK_EDGE 0x0080 /* rising/falling edge on pin delays sample */ +#define WM9712_MASK_SYNC 0x00c0 /* rising/falling edge on mask initiates sample */ +#define WM9712_RPU(i) (i&0x3f) /* internal pull up on pen detect (64k / rpu) */ +#define WM9712_PD(i) (0x1 << i) /* power management */ + +/* WM9712 Registers */ +#define AC97_WM9712_POWER 0x24 +#define AC97_WM9712_REV 0x58 + +/* WM9705 Bits */ +#define WM9705_PDEN 0x1000 /* measure only when pen is down */ +#define WM9705_PINV 0x0800 /* inverts sense of pen down output */ +#define WM9705_BSEN 0x0400 /* BUSY flag enable, pin47 is 1 when busy */ +#define WM9705_BINV 0x0200 /* invert BUSY (pin47) output */ +#define WM9705_WAIT 0x0100 /* wait until adc is read before next sample */ +#define WM9705_PIL 0x0080 /* current used for pressure measurement. set 400uA else 200uA */ +#define WM9705_PHIZ 0x0040 /* set PHONE and PCBEEP inputs to high impedance */ +#define WM9705_MASK_HI 0x0010 /* hi on mask stops conversions */ +#define WM9705_MASK_EDGE 0x0020 /* rising/falling edge on pin delays sample */ +#define WM9705_MASK_SYNC 0x0030 /* rising/falling edge on mask initiates sample */ +#define WM9705_PDD(i) (i & 0x000f) /* pen detect comparator threshold */ + + +/* WM9713 Bits */ +#define WM9713_PDPOL 0x0400 /* Pen down polarity */ +#define WM9713_POLL 0x0200 /* initiate a polling measurement */ +#define WM9713_CTC 0x0100 /* enable continuous mode */ +#define WM9713_ADCSEL_X 0x0002 /* X measurement */ +#define WM9713_ADCSEL_Y 0x0004 /* Y measurement */ +#define WM9713_ADCSEL_PRES 0x0008 /* Pressure measurement */ +#define WM9713_COO 0x0001 /* enable coordinate mode */ +#define WM9713_PDEN 0x0800 /* measure only when pen down */ +#define WM9713_ADCSEL_MASK 0x00fe /* ADC selection mask */ +#define WM9713_WAIT 0x0200 /* coordinate wait */ + +/* AUX ADC ID's */ +#define TS_COMP1 0x0 +#define TS_COMP2 0x1 +#define TS_BMON 0x2 +#define TS_WIPER 0x3 + +/* ID numbers */ +#define WM97XX_ID1 0x574d +#define WM9712_ID2 0x4c12 +#define WM9705_ID2 0x4c05 +#define WM9713_ID2 0x4c13 + +/* Codec GPIO's */ +#define WM97XX_MAX_GPIO 16 +#define WM97XX_GPIO_1 (1 << 1) +#define WM97XX_GPIO_2 (1 << 2) +#define WM97XX_GPIO_3 (1 << 3) +#define WM97XX_GPIO_4 (1 << 4) +#define WM97XX_GPIO_5 (1 << 5) +#define WM97XX_GPIO_6 (1 << 6) +#define WM97XX_GPIO_7 (1 << 7) +#define WM97XX_GPIO_8 (1 << 8) +#define WM97XX_GPIO_9 (1 << 9) +#define WM97XX_GPIO_10 (1 << 10) +#define WM97XX_GPIO_11 (1 << 11) +#define WM97XX_GPIO_12 (1 << 12) +#define WM97XX_GPIO_13 (1 << 13) +#define WM97XX_GPIO_14 (1 << 14) +#define WM97XX_GPIO_15 (1 << 15) + + +#define AC97_LINK_FRAME 21 /* time in uS for AC97 link frame */ + + +/*---------------- Return codes from sample reading functions ---------------*/ + +/* More data is available; call the sample gathering function again */ +#define RC_AGAIN 0x00000001 +/* The returned sample is valid */ +#define RC_VALID 0x00000002 +/* The pen is up (the first RC_VALID without RC_PENUP means pen is down) */ +#define RC_PENUP 0x00000004 +/* The pen is down (RC_VALID implies RC_PENDOWN, but sometimes it is helpful + to tell the handler that the pen is down but we don't know yet his coords, + so the handler should not sleep or wait for pendown irq) */ +#define RC_PENDOWN 0x00000008 + +/* The wm97xx driver provides a private API for writing platform-specific + * drivers. + */ + +/* The structure used to return arch specific sampled data into */ +struct wm97xx_data { + int x; + int y; + int p; +}; + +/* Codec GPIO status + */ +typedef enum { + WM97XX_GPIO_HIGH, + WM97XX_GPIO_LOW +} wm97xx_gpio_status_t; + +/* Codec GPIO direction + */ +typedef enum { + WM97XX_GPIO_IN, + WM97XX_GPIO_OUT +} wm97xx_gpio_dir_t; + +/* Codec GPIO polarity + */ +typedef enum { + WM97XX_GPIO_POL_HIGH, + WM97XX_GPIO_POL_LOW +} wm97xx_gpio_pol_t; + +/* Codec GPIO sticky + */ +typedef enum { + WM97XX_GPIO_STICKY, + WM97XX_GPIO_NOTSTICKY +} wm97xx_gpio_sticky_t; + +/* Codec GPIO wake + */ +typedef enum { + WM97XX_GPIO_WAKE, + WM97XX_GPIO_NOWAKE +} wm97xx_gpio_wake_t; + + +/* + * Digitiser ioctl commands + */ +#define WM97XX_DIG_START 0x1 +#define WM97XX_DIG_STOP 0x2 +#define WM97XX_PHY_INIT 0x3 +#define WM97XX_AUX_PREPARE 0x4 +#define WM97XX_DIG_RESTORE 0x5 + +struct wm97xx; +extern struct wm97xx_codec_drv wm97xx_codec; + +/* + * Codec driver interface - allows mapping to WM9705/12/13 and newer codecs + */ +struct wm97xx_codec_drv { + u16 id; + char *name; + int (*poll_sample) (struct wm97xx *, int adcsel, int *sample); /* read 1 sample */ + int (*poll_touch) (struct wm97xx *, struct wm97xx_data *); /* read X,Y,[P] in poll */ + int (*digitiser_ioctl) (struct wm97xx *, int cmd); + int (*acc_enable) (struct wm97xx *, int enable); +}; + + +/* Machine specific and accelerated touch operations */ +struct wm97xx_mach_ops { + + /* accelerated touch readback - coords are transmited on AC97 link */ + int acc_enabled; + void (*acc_pen_up) (struct wm97xx *); + int (*acc_pen_down) (struct wm97xx *); + int (*acc_startup) (struct wm97xx *); + void (*acc_shutdown) (struct wm97xx *); + + /* pre and post sample - can be used to minimise any analog noise */ + void (*pre_sample) (int); /* function to run before sampling */ + void (*post_sample) (int); /* function to run after sampling */ +}; + +struct wm97xx { + u16 dig[3], id, gpio[6], misc; /* Cached codec registers */ + u16 dig_save[3]; /* saved during aux reading */ + struct wm97xx_codec_drv *codec; /* attached codec driver*/ + struct input_dev* input_dev; /* touchscreen input device */ + struct snd_ac97 *ac97; /* ALSA codec access */ + struct device *dev; /* ALSA device */ + struct device *battery_dev; + struct device *touch_dev; + struct wm97xx_mach_ops *mach_ops; + struct mutex codec_mutex; + struct completion ts_init; + struct completion ts_exit; + struct task_struct *ts_task; + unsigned int pen_irq; /* Pen IRQ number in use */ + wait_queue_head_t pen_irq_wait; /* Pen IRQ wait queue */ + struct workqueue_struct *pen_irq_workq; + struct work_struct pen_event_work; + u16 acc_slot; /* AC97 slot used for acc touch data */ + u16 acc_rate; /* acc touch data rate */ + unsigned int ts_use_count; + unsigned pen_is_down:1; /* Pen is down */ + unsigned aux_waiting:1; /* aux measurement waiting */ + unsigned pen_probably_down:1; /* used in polling mode */ +}; + +/* Codec GPIO access (not supported on WM9705) + * This can be used to set/get codec GPIO and Virtual GPIO status. + */ +wm97xx_gpio_status_t wm97xx_get_gpio(struct wm97xx *wm, u32 gpio); +void wm97xx_set_gpio(struct wm97xx *wm, u32 gpio, + wm97xx_gpio_status_t status); +void wm97xx_config_gpio(struct wm97xx *wm, u32 gpio, + wm97xx_gpio_dir_t dir, + wm97xx_gpio_pol_t pol, + wm97xx_gpio_sticky_t sticky, + wm97xx_gpio_wake_t wake); + +/* codec AC97 IO access */ +int wm97xx_reg_read(struct wm97xx *wm, u16 reg); +void wm97xx_reg_write(struct wm97xx *wm, u16 reg, u16 val); + +/* aux adc readback */ +int wm97xx_read_aux_adc(struct wm97xx *wm, u16 adcsel); + +/* machine ops */ +int wm97xx_register_mach_ops(struct wm97xx *, struct wm97xx_mach_ops *); +void wm97xx_unregister_mach_ops(struct wm97xx *); + +extern struct bus_type wm97xx_bus_type; +#endif diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index a83e229..89b5730 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -53,3 +53,12 @@ config SND_PXA2XX_SOC_TOSA help Say Y if you want to add support for SoC audio on Sharp Zaurus SL-C6000x models (Tosa). + +config SND_PXA2XX_SOC_EM_X270 + tristate "SoC Audio support for CompuLab EM-x270" + depends on SND_PXA2XX_SOC && MACH_EM_X270 + select SND_PXA2XX_SOC_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for SoC audio on + CompuLab EM-x270. diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 78e0d6b..32375ac 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -12,9 +12,11 @@ snd-soc-corgi-objs := corgi.o snd-soc-poodle-objs := poodle.o snd-soc-tosa-objs := tosa.o snd-soc-spitz-objs := spitz.o +snd-soc-em-x270-objs := em-x270.o obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o +obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c new file mode 100644 index 0000000..9e891d0 --- /dev/null +++ b/sound/soc/pxa/em-x270.c @@ -0,0 +1,137 @@ +/* + * em-x270.c -- SoC audio for EM-X270 + * + * Copyright 2007 CompuLab, Ltd. + * + * Author: Mike Rapoport <mike@compulab.co.il> + * + * Copied from tosa.c: + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Richard Purdie <richard@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/hardware.h> +#include <asm/arch/audio.h> + +#include "../codecs/wm9712.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-ac97.h" + +static struct snd_soc_machine em_x270; + +#define EM_X270_HP 0 +#define EM_X270_MIC_INT 1 +#define EM_X270_HEADSET 2 +#define EM_X270_HP_OFF 3 +#define EM_X270_SPK_ON 0 +#define EM_X270_SPK_OFF 1 + + +static struct snd_soc_ops em_x270_ops = { +}; + +static const struct snd_kcontrol_new em_x270_controls[] = { +}; + +static int em_x270_ac97_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* add em_x270 specific controls */ + for (i = 0; i < ARRAY_SIZE(em_x270_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&em_x270_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_dai_link em_x270_dai[] = { + { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI], + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI], + .init = em_x270_ac97_init, + .ops = &em_x270_ops, + }, + { + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX], + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX], + .ops = &em_x270_ops, + }, +}; + +static struct snd_soc_machine em_x270 = { + .name = "EM-X270", + .dai_link = em_x270_dai, + .num_links = ARRAY_SIZE(em_x270_dai), +}; + +static struct snd_soc_device em_x270_snd_devdata = { + .machine = &em_x270, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm9712, +}; + +static struct platform_device *em_x270_snd_device; + +static int __init em_x270_init(void) +{ + int ret; + + if (!machine_is_em_x270()) + return -ENODEV; + + em_x270_snd_device = platform_device_alloc("soc-audio", -1); + if (!em_x270_snd_device) + return -ENOMEM; + + platform_set_drvdata(em_x270_snd_device, &em_x270_snd_devdata); + em_x270_snd_devdata.dev = &em_x270_snd_device->dev; + ret = platform_device_add(em_x270_snd_device); + + if (ret) + platform_device_put(em_x270_snd_device); + + return ret; +} + +static void __exit em_x270_exit(void) +{ + platform_device_unregister(em_x270_snd_device); +} + +module_init(em_x270_init); +module_exit(em_x270_exit); + +/* Module information */ +MODULE_AUTHOR("Mike Rapoport"); +MODULE_DESCRIPTION("ALSA SoC EM-X270"); +MODULE_LICENSE("GPL");