2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-04-03 19:45:15 +04:00
/*
* Compaq iPAQ h3xxx Atmel microcontroller companion support
*
* This is an Atmel AT90LS8535 with a special flashed - in firmware that
* implements the special protocol used by this driver .
*
* based on previous kernel 2.4 version by Andrew Christian
* Author : Alessandro Gardich < gremlin @ gremlin . it >
* Author : Dmitry Artamonow < mad_soft @ inbox . ru >
* Author : Linus Walleij < linus . walleij @ linaro . org >
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/pm.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/platform_device.h>
# include <linux/io.h>
# include <linux/mfd/core.h>
# include <linux/mfd/ipaq-micro.h>
# include <linux/string.h>
# include <linux/random.h>
# include <linux/slab.h>
# include <linux/list.h>
# include <mach/hardware.h>
static void ipaq_micro_trigger_tx ( struct ipaq_micro * micro )
{
struct ipaq_micro_txdev * tx = & micro - > tx ;
struct ipaq_micro_msg * msg = micro - > msg ;
int i , bp ;
u8 checksum ;
u32 val ;
bp = 0 ;
tx - > buf [ bp + + ] = CHAR_SOF ;
checksum = ( ( msg - > id & 0x0f ) < < 4 ) | ( msg - > tx_len & 0x0f ) ;
tx - > buf [ bp + + ] = checksum ;
for ( i = 0 ; i < msg - > tx_len ; i + + ) {
tx - > buf [ bp + + ] = msg - > tx_data [ i ] ;
checksum + = msg - > tx_data [ i ] ;
}
tx - > buf [ bp + + ] = checksum ;
tx - > len = bp ;
tx - > index = 0 ;
/* Enable interrupt */
val = readl ( micro - > base + UTCR3 ) ;
val | = UTCR3_TIE ;
writel ( val , micro - > base + UTCR3 ) ;
}
int ipaq_micro_tx_msg ( struct ipaq_micro * micro , struct ipaq_micro_msg * msg )
{
unsigned long flags ;
dev_dbg ( micro - > dev , " TX msg: %02x, %d bytes \n " , msg - > id , msg - > tx_len ) ;
spin_lock_irqsave ( & micro - > lock , flags ) ;
if ( micro - > msg ) {
list_add_tail ( & msg - > node , & micro - > queue ) ;
spin_unlock_irqrestore ( & micro - > lock , flags ) ;
return 0 ;
}
micro - > msg = msg ;
ipaq_micro_trigger_tx ( micro ) ;
spin_unlock_irqrestore ( & micro - > lock , flags ) ;
return 0 ;
}
EXPORT_SYMBOL ( ipaq_micro_tx_msg ) ;
static void micro_rx_msg ( struct ipaq_micro * micro , u8 id , int len , u8 * data )
{
int i ;
dev_dbg ( micro - > dev , " RX msg: %02x, %d bytes \n " , id , len ) ;
spin_lock ( & micro - > lock ) ;
switch ( id ) {
case MSG_VERSION :
case MSG_EEPROM_READ :
case MSG_EEPROM_WRITE :
case MSG_BACKLIGHT :
case MSG_NOTIFY_LED :
case MSG_THERMAL_SENSOR :
case MSG_BATTERY :
/* Handle synchronous messages */
if ( micro - > msg & & micro - > msg - > id = = id ) {
struct ipaq_micro_msg * msg = micro - > msg ;
memcpy ( msg - > rx_data , data , len ) ;
msg - > rx_len = len ;
complete ( & micro - > msg - > ack ) ;
if ( ! list_empty ( & micro - > queue ) ) {
micro - > msg = list_entry ( micro - > queue . next ,
struct ipaq_micro_msg ,
node ) ;
list_del_init ( & micro - > msg - > node ) ;
ipaq_micro_trigger_tx ( micro ) ;
} else
micro - > msg = NULL ;
dev_dbg ( micro - > dev , " OK RX message 0x%02x \n " , id ) ;
} else {
dev_err ( micro - > dev ,
" out of band RX message 0x%02x \n " , id ) ;
2014-07-21 19:08:55 +04:00
if ( ! micro - > msg )
2014-04-03 19:45:15 +04:00
dev_info ( micro - > dev , " no message queued \n " ) ;
else
dev_info ( micro - > dev , " expected message %02x \n " ,
micro - > msg - > id ) ;
}
break ;
case MSG_KEYBOARD :
if ( micro - > key )
micro - > key ( micro - > key_data , len , data ) ;
else
2014-07-21 19:08:55 +04:00
dev_dbg ( micro - > dev , " key message ignored, no handle \n " ) ;
2014-04-03 19:45:15 +04:00
break ;
case MSG_TOUCHSCREEN :
if ( micro - > ts )
micro - > ts ( micro - > ts_data , len , data ) ;
else
2014-07-21 19:08:55 +04:00
dev_dbg ( micro - > dev , " touchscreen message ignored, no handle \n " ) ;
2014-04-03 19:45:15 +04:00
break ;
default :
dev_err ( micro - > dev ,
" unknown msg %d [%d] " , id , len ) ;
for ( i = 0 ; i < len ; + + i )
pr_cont ( " 0x%02x " , data [ i ] ) ;
pr_cont ( " \n " ) ;
}
spin_unlock ( & micro - > lock ) ;
}
static void micro_process_char ( struct ipaq_micro * micro , u8 ch )
{
struct ipaq_micro_rxdev * rx = & micro - > rx ;
switch ( rx - > state ) {
case STATE_SOF : /* Looking for SOF */
if ( ch = = CHAR_SOF )
rx - > state = STATE_ID ; /* Next byte is the id and len */
break ;
case STATE_ID : /* Looking for id and len byte */
2014-07-21 19:08:55 +04:00
rx - > id = ( ch & 0xf0 ) > > 4 ;
2014-04-03 19:45:15 +04:00
rx - > len = ( ch & 0x0f ) ;
rx - > index = 0 ;
rx - > chksum = ch ;
rx - > state = ( rx - > len > 0 ) ? STATE_DATA : STATE_CHKSUM ;
break ;
case STATE_DATA : /* Looking for 'len' data bytes */
rx - > chksum + = ch ;
rx - > buf [ rx - > index ] = ch ;
if ( + + rx - > index = = rx - > len )
rx - > state = STATE_CHKSUM ;
break ;
case STATE_CHKSUM : /* Looking for the checksum */
if ( ch = = rx - > chksum )
micro_rx_msg ( micro , rx - > id , rx - > len , rx - > buf ) ;
rx - > state = STATE_SOF ;
break ;
}
}
static void micro_rx_chars ( struct ipaq_micro * micro )
{
u32 status , ch ;
while ( ( status = readl ( micro - > base + UTSR1 ) ) & UTSR1_RNE ) {
ch = readl ( micro - > base + UTDR ) ;
if ( status & UTSR1_PRE )
dev_err ( micro - > dev , " rx: parity error \n " ) ;
else if ( status & UTSR1_FRE )
dev_err ( micro - > dev , " rx: framing error \n " ) ;
else if ( status & UTSR1_ROR )
dev_err ( micro - > dev , " rx: overrun error \n " ) ;
micro_process_char ( micro , ch ) ;
}
}
static void ipaq_micro_get_version ( struct ipaq_micro * micro )
{
struct ipaq_micro_msg msg = {
. id = MSG_VERSION ,
} ;
ipaq_micro_tx_msg_sync ( micro , & msg ) ;
if ( msg . rx_len = = 4 ) {
memcpy ( micro - > version , msg . rx_data , 4 ) ;
micro - > version [ 4 ] = ' \0 ' ;
} else if ( msg . rx_len = = 9 ) {
memcpy ( micro - > version , msg . rx_data , 4 ) ;
micro - > version [ 4 ] = ' \0 ' ;
/* Bytes 4-7 are "pack", byte 8 is "boot type" */
} else {
dev_err ( micro - > dev ,
" illegal version message %d bytes \n " , msg . rx_len ) ;
}
}
static void ipaq_micro_eeprom_read ( struct ipaq_micro * micro ,
u8 address , u8 len , u8 * data )
{
struct ipaq_micro_msg msg = {
. id = MSG_EEPROM_READ ,
} ;
u8 i ;
for ( i = 0 ; i < len ; i + + ) {
msg . tx_data [ 0 ] = address + i ;
msg . tx_data [ 1 ] = 1 ;
msg . tx_len = 2 ;
ipaq_micro_tx_msg_sync ( micro , & msg ) ;
memcpy ( data + ( i * 2 ) , msg . rx_data , 2 ) ;
}
}
static char * ipaq_micro_str ( u8 * wchar , u8 len )
{
char retstr [ 256 ] ;
u8 i ;
for ( i = 0 ; i < len / 2 ; i + + )
retstr [ i ] = wchar [ i * 2 ] ;
return kstrdup ( retstr , GFP_KERNEL ) ;
}
static u16 ipaq_micro_to_u16 ( u8 * data )
{
return data [ 1 ] < < 8 | data [ 0 ] ;
}
2015-07-22 10:55:43 +03:00
static void __init ipaq_micro_eeprom_dump ( struct ipaq_micro * micro )
2014-04-03 19:45:15 +04:00
{
u8 dump [ 256 ] ;
char * str ;
ipaq_micro_eeprom_read ( micro , 0 , 128 , dump ) ;
str = ipaq_micro_str ( dump , 10 ) ;
if ( str ) {
2015-07-22 10:55:41 +03:00
dev_info ( micro - > dev , " HW version %s \n " , str ) ;
2014-04-03 19:45:15 +04:00
kfree ( str ) ;
}
str = ipaq_micro_str ( dump + 10 , 40 ) ;
if ( str ) {
dev_info ( micro - > dev , " serial number: %s \n " , str ) ;
/* Feed the random pool with this */
add_device_randomness ( str , strlen ( str ) ) ;
kfree ( str ) ;
}
str = ipaq_micro_str ( dump + 50 , 20 ) ;
if ( str ) {
dev_info ( micro - > dev , " module ID: %s \n " , str ) ;
kfree ( str ) ;
}
str = ipaq_micro_str ( dump + 70 , 10 ) ;
if ( str ) {
dev_info ( micro - > dev , " product revision: %s \n " , str ) ;
kfree ( str ) ;
}
dev_info ( micro - > dev , " product ID: %u \n " , ipaq_micro_to_u16 ( dump + 80 ) ) ;
dev_info ( micro - > dev , " frame rate: %u fps \n " ,
ipaq_micro_to_u16 ( dump + 82 ) ) ;
dev_info ( micro - > dev , " page mode: %u \n " , ipaq_micro_to_u16 ( dump + 84 ) ) ;
dev_info ( micro - > dev , " country ID: %u \n " , ipaq_micro_to_u16 ( dump + 86 ) ) ;
dev_info ( micro - > dev , " color display: %s \n " ,
ipaq_micro_to_u16 ( dump + 88 ) ? " yes " : " no " ) ;
dev_info ( micro - > dev , " ROM size: %u MiB \n " , ipaq_micro_to_u16 ( dump + 90 ) ) ;
dev_info ( micro - > dev , " RAM size: %u KiB \n " , ipaq_micro_to_u16 ( dump + 92 ) ) ;
dev_info ( micro - > dev , " screen: %u x %u \n " ,
ipaq_micro_to_u16 ( dump + 94 ) , ipaq_micro_to_u16 ( dump + 96 ) ) ;
}
static void micro_tx_chars ( struct ipaq_micro * micro )
{
struct ipaq_micro_txdev * tx = & micro - > tx ;
u32 val ;
while ( ( tx - > index < tx - > len ) & &
( readl ( micro - > base + UTSR1 ) & UTSR1_TNF ) ) {
writel ( tx - > buf [ tx - > index ] , micro - > base + UTDR ) ;
tx - > index + + ;
}
/* Stop interrupts */
val = readl ( micro - > base + UTCR3 ) ;
val & = ~ UTCR3_TIE ;
writel ( val , micro - > base + UTCR3 ) ;
}
static void micro_reset_comm ( struct ipaq_micro * micro )
{
struct ipaq_micro_rxdev * rx = & micro - > rx ;
u32 val ;
if ( micro - > msg )
complete ( & micro - > msg - > ack ) ;
/* Initialize Serial channel protocol frame */
rx - > state = STATE_SOF ; /* Reset the state machine */
/* Set up interrupts */
writel ( 0x01 , micro - > sdlc + 0x0 ) ; /* Select UART mode */
/* Clean up CR3 */
writel ( 0x0 , micro - > base + UTCR3 ) ;
/* Format: 8N1 */
writel ( UTCR0_8BitData | UTCR0_1StpBit , micro - > base + UTCR0 ) ;
/* Baud rate: 115200 */
writel ( 0x0 , micro - > base + UTCR1 ) ;
writel ( 0x1 , micro - > base + UTCR2 ) ;
/* Clear SR0 */
writel ( 0xff , micro - > base + UTSR0 ) ;
/* Enable RX int, disable TX int */
writel ( UTCR3_TXE | UTCR3_RXE | UTCR3_RIE , micro - > base + UTCR3 ) ;
val = readl ( micro - > base + UTCR3 ) ;
val & = ~ UTCR3_TIE ;
writel ( val , micro - > base + UTCR3 ) ;
}
static irqreturn_t micro_serial_isr ( int irq , void * dev_id )
{
struct ipaq_micro * micro = dev_id ;
struct ipaq_micro_txdev * tx = & micro - > tx ;
u32 status ;
status = readl ( micro - > base + UTSR0 ) ;
do {
if ( status & ( UTSR0_RID | UTSR0_RFS ) ) {
if ( status & UTSR0_RID )
/* Clear the Receiver IDLE bit */
writel ( UTSR0_RID , micro - > base + UTSR0 ) ;
micro_rx_chars ( micro ) ;
}
/* Clear break bits */
if ( status & ( UTSR0_RBB | UTSR0_REB ) )
writel ( status & ( UTSR0_RBB | UTSR0_REB ) ,
micro - > base + UTSR0 ) ;
if ( status & UTSR0_TFS )
micro_tx_chars ( micro ) ;
status = readl ( micro - > base + UTSR0 ) ;
} while ( ( ( tx - > index < tx - > len ) & & ( status & UTSR0_TFS ) ) | |
( status & ( UTSR0_RFS | UTSR0_RID ) ) ) ;
return IRQ_HANDLED ;
}
2014-05-13 14:58:40 +04:00
static const struct mfd_cell micro_cells [ ] = {
2014-04-03 19:45:15 +04:00
{ . name = " ipaq-micro-backlight " , } ,
{ . name = " ipaq-micro-battery " , } ,
{ . name = " ipaq-micro-keys " , } ,
{ . name = " ipaq-micro-ts " , } ,
{ . name = " ipaq-micro-leds " , } ,
} ;
2016-03-02 18:58:57 +03:00
static int __maybe_unused micro_resume ( struct device * dev )
2014-04-03 19:45:15 +04:00
{
struct ipaq_micro * micro = dev_get_drvdata ( dev ) ;
micro_reset_comm ( micro ) ;
mdelay ( 10 ) ;
return 0 ;
}
2015-07-22 10:55:43 +03:00
static int __init micro_probe ( struct platform_device * pdev )
2014-04-03 19:45:15 +04:00
{
struct ipaq_micro * micro ;
int ret ;
int irq ;
micro = devm_kzalloc ( & pdev - > dev , sizeof ( * micro ) , GFP_KERNEL ) ;
if ( ! micro )
return - ENOMEM ;
micro - > dev = & pdev - > dev ;
2023-02-08 12:33:54 +03:00
micro - > base = devm_platform_get_and_ioremap_resource ( pdev , 0 , NULL ) ;
2014-05-14 07:17:14 +04:00
if ( IS_ERR ( micro - > base ) )
return PTR_ERR ( micro - > base ) ;
2014-04-03 19:45:15 +04:00
2019-09-18 14:40:30 +03:00
micro - > sdlc = devm_platform_ioremap_resource ( pdev , 1 ) ;
2014-05-14 07:17:14 +04:00
if ( IS_ERR ( micro - > sdlc ) )
return PTR_ERR ( micro - > sdlc ) ;
2014-04-03 19:45:15 +04:00
micro_reset_comm ( micro ) ;
irq = platform_get_irq ( pdev , 0 ) ;
2022-04-12 11:53:05 +03:00
if ( irq < 0 )
2014-04-03 19:45:15 +04:00
return - EINVAL ;
ret = devm_request_irq ( & pdev - > dev , irq , micro_serial_isr ,
IRQF_SHARED , " ipaq-micro " ,
micro ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " unable to grab serial port IRQ \n " ) ;
return ret ;
} else
dev_info ( & pdev - > dev , " grabbed serial port IRQ \n " ) ;
spin_lock_init ( & micro - > lock ) ;
INIT_LIST_HEAD ( & micro - > queue ) ;
platform_set_drvdata ( pdev , micro ) ;
ret = mfd_add_devices ( & pdev - > dev , pdev - > id , micro_cells ,
ARRAY_SIZE ( micro_cells ) , NULL , 0 , NULL ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " error adding MFD cells " ) ;
return ret ;
}
/* Check version */
ipaq_micro_get_version ( micro ) ;
dev_info ( & pdev - > dev , " Atmel micro ASIC version %s \n " , micro - > version ) ;
ipaq_micro_eeprom_dump ( micro ) ;
return 0 ;
}
static const struct dev_pm_ops micro_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( NULL , micro_resume )
} ;
static struct platform_driver micro_device_driver = {
. driver = {
. name = " ipaq-h3xxx-micro " ,
. pm = & micro_dev_pm_ops ,
2015-07-22 10:55:43 +03:00
. suppress_bind_attrs = true ,
2014-04-03 19:45:15 +04:00
} ,
} ;
2015-07-22 10:55:43 +03:00
builtin_platform_driver_probe ( micro_device_driver , micro_probe ) ;