From: <z7...@us...> - 2007-01-08 10:24:31
|
Revision: 740 http://svn.sourceforge.net/hackndev/?rev=740&view=rev Author: z72ka Date: 2007-01-08 02:24:30 -0800 (Mon, 08 Jan 2007) Log Message: ----------- New touchcscreen/battery driver Added Paths: ----------- linux4palm/linux/trunk/arch/arm/mach-pxa/palmz72/palmz72_ac97.c Added: linux4palm/linux/trunk/arch/arm/mach-pxa/palmz72/palmz72_ac97.c =================================================================== --- linux4palm/linux/trunk/arch/arm/mach-pxa/palmz72/palmz72_ac97.c (rev 0) +++ linux4palm/linux/trunk/arch/arm/mach-pxa/palmz72/palmz72_ac97.c 2007-01-08 10:24:30 UTC (rev 740) @@ -0,0 +1,552 @@ +/* + * linux/arch/arm/mach-pxa/palmz72/palmz72_ac97.c + * + * Touchscreen/battery driver for Palm Zire 72 WM9712 AC97 codec + * Author: Jan Herman <2h...@se...> + * Based on palmld_ac97.c code from Alex Osborne + * + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/battery.h> + +#include <asm/apm.h> +#include <asm/delay.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/irqs.h> + +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/ac97_codec.h> +#include <asm/arch/palmz72-ac97.h> +#include <asm/arch/palmz72-gpio.h> +#include <asm/arch/palmz72-init.h> + + +#define X_AXIS_MAX 3900 +#define X_AXIS_MIN 350 +#define Y_AXIS_MAX 3750 +#define Y_AXIS_MIN 320 +#define PRESSURE_MIN 0 +#define PRESSURE_MAX 300 + +#define DIG2_INIT 0x0001 /* initial value for digitiser2 reg */ + +#define DEFAULT_PRESSURE_TRESHOLD 47200 /* default pressure treshold for pen up/down */ +#define DEFAULT_X_AXIS_FUZZ 5 /* default x axis noise treshold */ +#define DEFAULT_Y_AXIS_FUZZ 40 /* default y axis noise treshold */ +#define PRESSURE_FUZZ 4 /* default pressure noise treshold */ + +#define palmz72_ac97_WORK_QUEUE_NAME "palmz72_ac97_workqueue" + + +/* module parameters */ + +static int ptrsh = DEFAULT_PRESSURE_TRESHOLD; +module_param(ptrsh, int, 0); +MODULE_PARM_DESC(ptrsh, "pressure treshold for pen up/down"); + +static int dbglvl = 0; // debug disabled +module_param(dbglvl, int, 0); +MODULE_PARM_DESC(dbglvl, "debug level (0 is disabled)"); + +static int xdjtrsh = DEFAULT_X_AXIS_FUZZ; +module_param(xdjtrsh, int, 0); +MODULE_PARM_DESC(xdjtrsh, "treshold for x axis jitter"); + +static int ydjtrsh = DEFAULT_Y_AXIS_FUZZ; +module_param(ydjtrsh, int, 0); +MODULE_PARM_DESC(ydjtrsh, "treshold for y axis jitter"); + + +static DECLARE_MUTEX_LOCKED(queue_sem); +static DECLARE_MUTEX(digitiser_sem); +static DECLARE_MUTEX(battery_update_mutex); + +static struct workqueue_struct *palmz72_ac97_workqueue; +static struct work_struct palmz72_ac97_irq_task; + +struct input_dev *palmz72_ac97_input; +struct device *palmz72_ac97_dev; + +static ac97_t *ac97; + +static int battery_registered = 0; +static unsigned long last_battery_update = 0; +static int current_voltage; +static int previous_voltage; +static u16 d2base; + +/* + * ac97 codec + */ + +void wm97xx_gpio_func(int gpio, int func) +{ + int GEn; + GEn = ac97->bus->ops->read(ac97, 0x56); + if(func) + GEn |= gpio; + else + GEn &= ~gpio; + ac97->bus->ops->write(ac97, 0x56, GEn); +} + + +void wm97xx_gpio_mode(int gpio, int config, int polarity, int sticky, int wakeup) +{ + int GCn, GPn, GSn, GWn; + GCn = ac97->bus->ops->read(ac97, 0x4c); + GPn = ac97->bus->ops->read(ac97, 0x4e); + GSn = ac97->bus->ops->read(ac97, 0x50); + GWn = ac97->bus->ops->read(ac97, 0x52); + + if(config) + GCn |= gpio; + else + GCn &= ~gpio; + + if(polarity) + GPn |= gpio; + else + GPn &= ~gpio; + + if(sticky) + GSn |= gpio; + else + GSn &= ~gpio; + + if(wakeup) + GWn |= gpio; + else + GWn &= ~gpio; + + ac97->bus->ops->write(ac97, 0x4c, GCn); + ac97->bus->ops->write(ac97, 0x4e, GPn); + ac97->bus->ops->write(ac97, 0x50, GSn); + ac97->bus->ops->write(ac97, 0x52, GWn); +} + + +static void wm97xx_set_digitiser_power(int value) +{ + ac97->bus->ops->write(ac97, AC97_WM97XX_DIGITISER2, d2base | value); +} + + +/* + * note: for the TX there's some code that gets enabled in linux/sound/pxa2xx-ac97.c + * (ifdef CONFIG_MACH_PALMZ72) that tries to implement some recommended procedure for + * reading/writing reg 0x54 from a Intel's document + * (PXA27x Specification Update: 28007109.pdf sec.: E54) + */ + +static int palmz72_ac97_take_reading(int adcsel) +{ + + int timeout = 15; + u16 r76 = 0; + u16 r7a; + + r76 |= adcsel; // set ADCSEL (ADC source) + r76 |= WM97XX_DELAY(3); // set settling time delay + r76 &= ~(1<<11); // COO = 0 (single measurement) + r76 &= ~(1<<10); // CTC = 0 (polling mode) + r76 |= (1<<15); // initiate measurement (POLL) + + ac97->bus->ops->write(ac97, 0x76, r76); + + // wait settling time + udelay ((3 * AC97_LINK_FRAME) + 167); + + // wait for POLL to go low + while((ac97->bus->ops->read(ac97, 0x76) & 0x8000) && timeout){ + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout == 0){ + printk("palmz72_ac97: discarding reading due to POLL wait timout on 0x76\n"); + return 0; + } + + r7a = ac97->bus->ops->read(ac97, 0x7a); + + if ((r7a & WM97XX_ADCSEL_MASK) != adcsel){ + printk("palmz72_ac97: discarding reading -> wrong ADC source\n"); + return 0; + } + + return (int) r7a; + +} + + +static void palmz72_ac97_pendown(void) +{ + int xread, yread, pressure; + int valid_coords=0, btn_pressed = 0; + + /* take readings until the pen goes up */ + do { + /* take readings */ + xread = palmz72_ac97_take_reading(WM97XX_ADCSEL_X); + yread = palmz72_ac97_take_reading(WM97XX_ADCSEL_Y); + pressure = palmz72_ac97_take_reading(WM97XX_ADCSEL_PRES); + + // printk("Pressure is %d:\n", pressure); + + valid_coords = (xread & 0xfff) && (yread & 0xfff) && (pressure & 0xfff); + + if(valid_coords && (pressure < ptrsh)) { + btn_pressed = 1; + input_report_key(palmz72_ac97_input, BTN_TOUCH, 1); + input_report_abs(palmz72_ac97_input, ABS_X, xread & 0xfff); + input_report_abs(palmz72_ac97_input, ABS_Y, yread & 0xfff); + input_report_abs(palmz72_ac97_input, ABS_PRESSURE, pressure & 0xfff); + input_sync(palmz72_ac97_input); + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/100); + set_current_state(TASK_RUNNING); + } + } while( valid_coords ); + + + if (btn_pressed) { + input_report_key(palmz72_ac97_input, BTN_TOUCH, 0); + input_report_abs(palmz72_ac97_input, ABS_X, 0); + input_report_abs(palmz72_ac97_input, ABS_Y, 0); + input_report_abs(palmz72_ac97_input, ABS_PRESSURE, 0); + input_sync(palmz72_ac97_input); + } + +} + + +static void palmz72_ac97_irq_work(void *data) +{ + u16 levels; + u16 polarity; + + levels = ac97->bus->ops->read(ac97, 0x54); + polarity = ac97->bus->ops->read(ac97, 0x4e); + + if(polarity & levels & WM97XX_GPIO_13) { + // power up digitiser: + down(&digitiser_sem); + wm97xx_set_digitiser_power(WM97XX_PRP_DET_DIG); + + palmz72_ac97_pendown(); + + /* power down digitiser to conserve power */ + wm97xx_set_digitiser_power(WM97XX_PRP_DET); + ac97->bus->ops->write(ac97, 0x4e, polarity & ~WM97XX_GPIO_13); + up(&digitiser_sem); + } + else { + ac97->bus->ops->write(ac97, 0x4e, polarity | WM97XX_GPIO_13); + } + + ac97->bus->ops->write(ac97, 0x54, levels & ~WM97XX_GPIO_13); + + udelay(1); + up(&queue_sem); + enable_irq(IRQ_GPIO_PALMZ72_WM9712_IRQ); +} + + +static irqreturn_t palmz72_ac97_irq_handler(int irq, void *dev_id, struct pt_regs *regs) +{ + if (down_trylock(&queue_sem) == 0){ + disable_irq(IRQ_GPIO_PALMZ72_WM9712_IRQ); + queue_work(palmz72_ac97_workqueue, &palmz72_ac97_irq_task); + } + + return IRQ_HANDLED; +} + +/* battery */ + + +void palmz72_battery_read_adc(int force) +{ + u16 vread; + + if(!force && ((last_battery_update + 10 *HZ) > jiffies)) + return; + + if(down_trylock(&battery_update_mutex)) + return; + + down(&digitiser_sem); + wm97xx_set_digitiser_power(WM97XX_PRP_DET_DIG); + vread = palmz72_ac97_take_reading(WM97XX_ADCSEL_BMON); + wm97xx_set_digitiser_power(WM97XX_PRP_DET); + up(&digitiser_sem); + + previous_voltage = current_voltage; + current_voltage = vread & 0xfff; + last_battery_update = jiffies; + + up(&battery_update_mutex); +} + + +int palmz72_battery_min_voltage(struct battery *b) +{ + return PALMZ72_BAT_MIN_VOLTAGE; +} + + +int palmz72_battery_max_voltage(struct battery *b) +{ + return PALMZ72_BAT_MAX_VOLTAGE; /* mV */ +} + +// V-batt = ADCSEL_BMON * 1,889 + 767,8 [mV] + +int palmz72_battery_get_voltage(struct battery *b) +{ + if (battery_registered){ + palmz72_battery_read_adc(0); + //printk("Battery [mV]: %d\n", current_voltage + PALMZ72_BMON_TO_VBATT ); + return current_voltage * 1889/1000 + 7678/10; + } + else{ + printk("palmz72_battery: cannot get voltage -> battery driver unregistered\n"); + return 0; + } +} + + +int palmz72_battery_get_status(struct battery *b) +{ + int ac_connected = GET_GPIO(GPIO_NR_PALMZ72_POWER_DETECT); + int usb_connected = !GET_GPIO(GPIO_NR_PALMZ72_USB_DETECT); + + if (current_voltage <= 0) + return BATTERY_STATUS_UNKNOWN; + + if (ac_connected || usb_connected){ + // TODO: ok maybe this is too stupid ... to be reviewed + if ( ( current_voltage > previous_voltage ) || (current_voltage <= PALMZ72_BAT_MAX_VOLTAGE) ) + return BATTERY_STATUS_CHARGING; + return BATTERY_STATUS_NOT_CHARGING; + } + else + return BATTERY_STATUS_DISCHARGING; +} + + +struct battery palmz72_battery = { + .name = "palmz72_battery", + .id = "battery0", + .get_min_voltage = palmz72_battery_min_voltage, + .get_max_voltage = palmz72_battery_max_voltage, + .get_voltage = palmz72_battery_get_voltage, + .get_status = palmz72_battery_get_status, +}; + + + + +static int __init palmz72_ac97_probe(struct device *dev) +{ + int err; + u16 d2 = DIG2_INIT; // init d1 too? + + if(!machine_is_palmz72()) + return -ENODEV; + + ac97 = to_ac97_t(dev); + + set_irq_type(IRQ_GPIO_PALMZ72_WM9712_IRQ, IRQT_RISING); + + err = request_irq(IRQ_GPIO_PALMZ72_WM9712_IRQ, palmz72_ac97_irq_handler, + SA_INTERRUPT, "WM9712 pendown IRQ", dev); + + if(err) { + printk(KERN_ERR "palmz72_ac97_probe: cannot request pen down IRQ\n"); + return -1; + } + + /* reset levels */ + ac97->bus->ops->write(ac97, 0x54, 0); + + /* disable digitiser to save power, enable pen-down detect */ + d2 |= WM97XX_PRP_DET; + d2base = d2; + ac97->bus->ops->write(ac97, AC97_WM97XX_DIGITISER2, d2base); + + /* enable interrupts on codec's gpio 2 (connected to cpu gpio 27) */ + wm97xx_gpio_mode(WM97XX_GPIO_2, WM97XX_GPIO_OUT, WM97XX_GPIO_POL_HIGH, + WM97XX_GPIO_NOTSTICKY, WM97XX_GPIO_NOWAKE); + wm97xx_gpio_func(WM97XX_GPIO_2, 0); + + /* enable pen detect interrupt */ + wm97xx_gpio_mode(WM97XX_GPIO_13, WM97XX_GPIO_OUT, WM97XX_GPIO_POL_HIGH, + WM97XX_GPIO_STICKY, WM97XX_GPIO_WAKE); + + /* turn off irq gpio inverting */ + ac97->bus->ops->write(ac97, 0x58, ac97->bus->ops->read(ac97, 0x58)&~1); + + /* turn on the digitiser and pen down detector */ + ac97->bus->ops->write(ac97, AC97_WM97XX_DIGITISER2, d2base | WM97XX_PRP_DETW); + + /* setup the input device */ + palmz72_ac97_input = input_allocate_device(); + if (palmz72_ac97_input == NULL){ + printk ("palmz72_ac97_probe: cannot allocate input device\n"); + return -ENOMEM; + } + + palmz72_ac97_input->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + + palmz72_ac97_input->keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + input_set_abs_params(palmz72_ac97_input, ABS_X, X_AXIS_MIN, X_AXIS_MAX, xdjtrsh, 0); + input_set_abs_params(palmz72_ac97_input, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, ydjtrsh, 0); + input_set_abs_params(palmz72_ac97_input, ABS_PRESSURE, PRESSURE_MIN, PRESSURE_MAX, PRESSURE_FUZZ, 0); + + palmz72_ac97_input->name = "palmz72 touchscreen"; + palmz72_ac97_input->dev = dev; + palmz72_ac97_input->id.bustype = BUS_HOST; + input_register_device(palmz72_ac97_input); + + /* register battery */ + + if(battery_class_register(&palmz72_battery)) { + printk(KERN_ERR "palmz72_ac97_probe: could not register battery class\n"); + } + else{ + battery_registered = 1; + } + + /* setup work queue */ + palmz72_ac97_workqueue = create_workqueue(palmz72_ac97_WORK_QUEUE_NAME); + INIT_WORK(&palmz72_ac97_irq_task, palmz72_ac97_irq_work, dev); + + up(&queue_sem); + return 0; +} + +static int palmz72_ac_is_connected (void){ + int ret = !(GET_GPIO(GPIO_NR_PALMZ72_POWER_DETECT)); + if (ret) + ret = 1; + else + ret = 0; + + return ret; +} + +/* APM */ +static void palmz72_apm_get_power_status(struct apm_power_info *info) +{ + int min, max, curr, percent; + + curr = palmz72_battery_get_voltage(NULL); + min = palmz72_battery_min_voltage(NULL); + max = palmz72_battery_max_voltage(NULL); + + curr = curr - min; + if (curr < 0) curr = 0; + max = max - min; + + percent = curr*100/max; + + info->battery_life = percent; + + info->ac_line_status = palmz72_ac_is_connected() ? APM_AC_ONLINE : APM_AC_OFFLINE; + + if (info->ac_line_status) { + info->battery_status = APM_BATTERY_STATUS_CHARGING; + } else { + if (percent > 50) + info->battery_status = APM_BATTERY_STATUS_HIGH; + else if (percent < 5) + info->battery_status = APM_BATTERY_STATUS_CRITICAL; + else + info->battery_status = APM_BATTERY_STATUS_LOW; + } + + info->time = percent * PALMZ72_MAX_LIFE_MINS/100; + info->units = APM_UNITS_MINS; +} + +typedef void (*apm_get_power_status_t)(struct apm_power_info*); + +int set_apm_get_power_status(apm_get_power_status_t t) +{ + apm_get_power_status = t; + + return 0; +} + + +/* end of APM implementing */ + +static int palmz72_ac97_remove (struct device *dev) +{ + // TODO: stop running tasks if any? + + battery_class_unregister(&palmz72_battery); + ac97 = NULL; + input_unregister_device(palmz72_ac97_input); + return 0; +} + + +static struct device_driver palmz72_ac97_driver = { + .name = "palmz72_ac97 (WM9712)", + .bus = &ac97_bus_type, + .owner = THIS_MODULE, + .probe = palmz72_ac97_probe, + .remove = palmz72_ac97_remove, + +#ifdef CONFIG_PM + .suspend = NULL, + .resume = NULL, +#endif +}; + + +static int __init palmz72_ac97_init(void) +{ + driver_register(&palmz72_ac97_driver); + +/* register battery to APM layer */ +#ifdef CONFIG_PM + apm_get_power_status = palmz72_apm_get_power_status; + return 0; +#endif + +} + + +static void __exit palmz72_ac97_exit(void) +{ + driver_unregister(&palmz72_ac97_driver); +} + + +module_init(palmz72_ac97_init); +module_exit(palmz72_ac97_exit); + +MODULE_AUTHOR ("Jan Herman <2h...@se...>"); +MODULE_DESCRIPTION ("WM9712 AC97 codec support for PalmOne Zire 72"); +MODULE_LICENSE ("GPL"); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |