linux/arch/arm/mach-pxa/corgi_ssp.c
Paul Sokolovsky 8f1bf8743c [ARM] 3760/1: This patch adds timeouts while working with SSP registers. Such timeouts were en
Patch from Paul Sokolovsky

This patch adds timeouts while working with SSP registers. Such
timeouts were envisioned by docstrings in ssp.c, but were not
implemented. There were actual lockups while accessing
touchscreen for iPaqs h1910, h4000 due to lack of the timeouts.
This is updated version of previously submitted patch: 3738/1.

Signed-off-by: Paul Sokolovsky <pmiscml@gmail.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2006-08-27 12:54:56 +01:00

275 lines
7.2 KiB
C

/*
* SSP control code for Sharp Corgi devices
*
* Copyright (c) 2004-2005 Richard Purdie
*
* 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/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <asm/hardware.h>
#include <asm/mach-types.h>
#include <asm/arch/ssp.h>
#include <asm/arch/pxa-regs.h>
#include "sharpsl.h"
static DEFINE_SPINLOCK(corgi_ssp_lock);
static struct ssp_dev corgi_ssp_dev;
static struct ssp_state corgi_ssp_state;
static struct corgissp_machinfo *ssp_machinfo;
/*
* There are three devices connected to the SSP interface:
* 1. A touchscreen controller (TI ADS7846 compatible)
* 2. An LCD contoller (with some Backlight functionality)
* 3. A battery moinitoring IC (Maxim MAX1111)
*
* Each device uses a different speed/mode of communication.
*
* The touchscreen is very sensitive and the most frequently used
* so the port is left configured for this.
*
* Devices are selected using Chip Selects on GPIOs.
*/
/*
* ADS7846 Routines
*/
unsigned long corgi_ssp_ads7846_putget(ulong data)
{
unsigned long flag;
u32 ret = 0;
spin_lock_irqsave(&corgi_ssp_lock, flag);
if (ssp_machinfo->cs_ads7846 >= 0)
GPCR(ssp_machinfo->cs_ads7846) = GPIO_bit(ssp_machinfo->cs_ads7846);
ssp_write_word(&corgi_ssp_dev,data);
ssp_read_word(&corgi_ssp_dev, &ret);
if (ssp_machinfo->cs_ads7846 >= 0)
GPSR(ssp_machinfo->cs_ads7846) = GPIO_bit(ssp_machinfo->cs_ads7846);
spin_unlock_irqrestore(&corgi_ssp_lock, flag);
return ret;
}
/*
* NOTE: These functions should always be called in interrupt context
* and use the _lock and _unlock functions. They are very time sensitive.
*/
void corgi_ssp_ads7846_lock(void)
{
spin_lock(&corgi_ssp_lock);
if (ssp_machinfo->cs_ads7846 >= 0)
GPCR(ssp_machinfo->cs_ads7846) = GPIO_bit(ssp_machinfo->cs_ads7846);
}
void corgi_ssp_ads7846_unlock(void)
{
if (ssp_machinfo->cs_ads7846 >= 0)
GPSR(ssp_machinfo->cs_ads7846) = GPIO_bit(ssp_machinfo->cs_ads7846);
spin_unlock(&corgi_ssp_lock);
}
void corgi_ssp_ads7846_put(ulong data)
{
ssp_write_word(&corgi_ssp_dev,data);
}
unsigned long corgi_ssp_ads7846_get(void)
{
u32 ret = 0;
ssp_read_word(&corgi_ssp_dev, &ret);
return ret;
}
EXPORT_SYMBOL(corgi_ssp_ads7846_putget);
EXPORT_SYMBOL(corgi_ssp_ads7846_lock);
EXPORT_SYMBOL(corgi_ssp_ads7846_unlock);
EXPORT_SYMBOL(corgi_ssp_ads7846_put);
EXPORT_SYMBOL(corgi_ssp_ads7846_get);
/*
* LCD/Backlight Routines
*/
unsigned long corgi_ssp_dac_put(ulong data)
{
unsigned long flag, sscr1 = SSCR1_SPH;
u32 tmp;
spin_lock_irqsave(&corgi_ssp_lock, flag);
if (machine_is_spitz() || machine_is_akita() || machine_is_borzoi())
sscr1 = 0;
ssp_disable(&corgi_ssp_dev);
ssp_config(&corgi_ssp_dev, (SSCR0_Motorola | (SSCR0_DSS & 0x07 )), sscr1, 0, SSCR0_SerClkDiv(ssp_machinfo->clk_lcdcon));
ssp_enable(&corgi_ssp_dev);
if (ssp_machinfo->cs_lcdcon >= 0)
GPCR(ssp_machinfo->cs_lcdcon) = GPIO_bit(ssp_machinfo->cs_lcdcon);
ssp_write_word(&corgi_ssp_dev,data);
/* Read null data back from device to prevent SSP overflow */
ssp_read_word(&corgi_ssp_dev, &tmp);
if (ssp_machinfo->cs_lcdcon >= 0)
GPSR(ssp_machinfo->cs_lcdcon) = GPIO_bit(ssp_machinfo->cs_lcdcon);
ssp_disable(&corgi_ssp_dev);
ssp_config(&corgi_ssp_dev, (SSCR0_National | (SSCR0_DSS & 0x0b )), 0, 0, SSCR0_SerClkDiv(ssp_machinfo->clk_ads7846));
ssp_enable(&corgi_ssp_dev);
spin_unlock_irqrestore(&corgi_ssp_lock, flag);
return 0;
}
void corgi_ssp_lcdtg_send(u8 adrs, u8 data)
{
corgi_ssp_dac_put(((adrs & 0x07) << 5) | (data & 0x1f));
}
void corgi_ssp_blduty_set(int duty)
{
corgi_ssp_lcdtg_send(0x02,duty);
}
EXPORT_SYMBOL(corgi_ssp_lcdtg_send);
EXPORT_SYMBOL(corgi_ssp_blduty_set);
/*
* Max1111 Routines
*/
int corgi_ssp_max1111_get(ulong data)
{
unsigned long flag;
long voltage = 0, voltage1 = 0, voltage2 = 0;
spin_lock_irqsave(&corgi_ssp_lock, flag);
if (ssp_machinfo->cs_max1111 >= 0)
GPCR(ssp_machinfo->cs_max1111) = GPIO_bit(ssp_machinfo->cs_max1111);
ssp_disable(&corgi_ssp_dev);
ssp_config(&corgi_ssp_dev, (SSCR0_Motorola | (SSCR0_DSS & 0x07 )), 0, 0, SSCR0_SerClkDiv(ssp_machinfo->clk_max1111));
ssp_enable(&corgi_ssp_dev);
udelay(1);
/* TB1/RB1 */
ssp_write_word(&corgi_ssp_dev,data);
ssp_read_word(&corgi_ssp_dev, (u32*)&voltage1); /* null read */
/* TB12/RB2 */
ssp_write_word(&corgi_ssp_dev,0);
ssp_read_word(&corgi_ssp_dev, (u32*)&voltage1);
/* TB13/RB3*/
ssp_write_word(&corgi_ssp_dev,0);
ssp_read_word(&corgi_ssp_dev, (u32*)&voltage2);
ssp_disable(&corgi_ssp_dev);
ssp_config(&corgi_ssp_dev, (SSCR0_National | (SSCR0_DSS & 0x0b )), 0, 0, SSCR0_SerClkDiv(ssp_machinfo->clk_ads7846));
ssp_enable(&corgi_ssp_dev);
if (ssp_machinfo->cs_max1111 >= 0)
GPSR(ssp_machinfo->cs_max1111) = GPIO_bit(ssp_machinfo->cs_max1111);
spin_unlock_irqrestore(&corgi_ssp_lock, flag);
if (voltage1 & 0xc0 || voltage2 & 0x3f)
voltage = -1;
else
voltage = ((voltage1 << 2) & 0xfc) | ((voltage2 >> 6) & 0x03);
return voltage;
}
EXPORT_SYMBOL(corgi_ssp_max1111_get);
/*
* Support Routines
*/
void __init corgi_ssp_set_machinfo(struct corgissp_machinfo *machinfo)
{
ssp_machinfo = machinfo;
}
static int __init corgi_ssp_probe(struct platform_device *dev)
{
int ret;
/* Chip Select - Disable All */
if (ssp_machinfo->cs_lcdcon >= 0)
pxa_gpio_mode(ssp_machinfo->cs_lcdcon | GPIO_OUT | GPIO_DFLT_HIGH);
if (ssp_machinfo->cs_max1111 >= 0)
pxa_gpio_mode(ssp_machinfo->cs_max1111 | GPIO_OUT | GPIO_DFLT_HIGH);
if (ssp_machinfo->cs_ads7846 >= 0)
pxa_gpio_mode(ssp_machinfo->cs_ads7846 | GPIO_OUT | GPIO_DFLT_HIGH);
ret = ssp_init(&corgi_ssp_dev, ssp_machinfo->port, 0);
if (ret)
printk(KERN_ERR "Unable to register SSP handler!\n");
else {
ssp_disable(&corgi_ssp_dev);
ssp_config(&corgi_ssp_dev, (SSCR0_National | (SSCR0_DSS & 0x0b )), 0, 0, SSCR0_SerClkDiv(ssp_machinfo->clk_ads7846));
ssp_enable(&corgi_ssp_dev);
}
return ret;
}
static int corgi_ssp_remove(struct platform_device *dev)
{
ssp_exit(&corgi_ssp_dev);
return 0;
}
static int corgi_ssp_suspend(struct platform_device *dev, pm_message_t state)
{
ssp_flush(&corgi_ssp_dev);
ssp_save_state(&corgi_ssp_dev,&corgi_ssp_state);
return 0;
}
static int corgi_ssp_resume(struct platform_device *dev)
{
if (ssp_machinfo->cs_lcdcon >= 0)
GPSR(ssp_machinfo->cs_lcdcon) = GPIO_bit(ssp_machinfo->cs_lcdcon); /* High - Disable LCD Control/Timing Gen */
if (ssp_machinfo->cs_max1111 >= 0)
GPSR(ssp_machinfo->cs_max1111) = GPIO_bit(ssp_machinfo->cs_max1111); /* High - Disable MAX1111*/
if (ssp_machinfo->cs_ads7846 >= 0)
GPSR(ssp_machinfo->cs_ads7846) = GPIO_bit(ssp_machinfo->cs_ads7846); /* High - Disable ADS7846*/
ssp_restore_state(&corgi_ssp_dev,&corgi_ssp_state);
ssp_enable(&corgi_ssp_dev);
return 0;
}
static struct platform_driver corgissp_driver = {
.probe = corgi_ssp_probe,
.remove = corgi_ssp_remove,
.suspend = corgi_ssp_suspend,
.resume = corgi_ssp_resume,
.driver = {
.name = "corgi-ssp",
},
};
int __init corgi_ssp_init(void)
{
return platform_driver_register(&corgissp_driver);
}
arch_initcall(corgi_ssp_init);