2019-05-27 08:55:01 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-08-26 15:17:49 +01:00
/*
* Copyright ( C ) 2016 Imagination Technologies
2017-10-25 17:04:33 -07:00
* Author : Paul Burton < paul . burton @ mips . com >
2016-08-26 15:17:49 +01:00
*/
# include <linux/kernel.h>
# include <linux/io.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/of_address.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/slab.h>
2021-10-19 16:45:05 +02:00
# include "line-display.h"
2016-08-26 15:17:49 +01:00
struct img_ascii_lcd_ctx ;
/**
* struct img_ascii_lcd_config - Configuration information about an LCD model
* @ num_chars : the number of characters the LCD can display
* @ external_regmap : true if registers are in a system controller , else false
* @ update : function called to update the LCD
*/
struct img_ascii_lcd_config {
unsigned int num_chars ;
bool external_regmap ;
2021-10-19 16:45:05 +02:00
void ( * update ) ( struct linedisp * linedisp ) ;
2016-08-26 15:17:49 +01:00
} ;
/**
* struct img_ascii_lcd_ctx - Private data structure
* @ base : the base address of the LCD registers
* @ regmap : the regmap through which LCD registers are accessed
* @ offset : the offset within regmap to the start of the LCD registers
* @ cfg : pointer to the LCD model configuration
2021-10-19 16:45:05 +02:00
* @ linedisp : line display structure
2016-08-26 15:17:49 +01:00
* @ curr : the string currently displayed on the LCD
*/
struct img_ascii_lcd_ctx {
union {
void __iomem * base ;
struct regmap * regmap ;
} ;
u32 offset ;
const struct img_ascii_lcd_config * cfg ;
2021-10-19 16:45:05 +02:00
struct linedisp linedisp ;
2016-08-26 15:17:49 +01:00
char curr [ ] __aligned ( 8 ) ;
} ;
/*
* MIPS Boston development board
*/
2021-10-19 16:45:05 +02:00
static void boston_update ( struct linedisp * linedisp )
2016-08-26 15:17:49 +01:00
{
2021-10-19 16:45:05 +02:00
struct img_ascii_lcd_ctx * ctx =
container_of ( linedisp , struct img_ascii_lcd_ctx , linedisp ) ;
2016-08-26 15:17:49 +01:00
ulong val ;
# if BITS_PER_LONG == 64
val = * ( ( u64 * ) & ctx - > curr [ 0 ] ) ;
__raw_writeq ( val , ctx - > base ) ;
# elif BITS_PER_LONG == 32
val = * ( ( u32 * ) & ctx - > curr [ 0 ] ) ;
__raw_writel ( val , ctx - > base ) ;
val = * ( ( u32 * ) & ctx - > curr [ 4 ] ) ;
__raw_writel ( val , ctx - > base + 4 ) ;
# else
# error Not 32 or 64 bit
# endif
}
static struct img_ascii_lcd_config boston_config = {
. num_chars = 8 ,
. update = boston_update ,
} ;
/*
* MIPS Malta development board
*/
2021-10-19 16:45:05 +02:00
static void malta_update ( struct linedisp * linedisp )
2016-08-26 15:17:49 +01:00
{
2021-10-19 16:45:05 +02:00
struct img_ascii_lcd_ctx * ctx =
container_of ( linedisp , struct img_ascii_lcd_ctx , linedisp ) ;
2016-08-26 15:17:49 +01:00
unsigned int i ;
2018-02-19 16:47:52 +01:00
int err = 0 ;
2016-08-26 15:17:49 +01:00
2021-10-19 16:45:05 +02:00
for ( i = 0 ; i < linedisp - > num_chars ; i + + ) {
2016-08-26 15:17:49 +01:00
err = regmap_write ( ctx - > regmap ,
ctx - > offset + ( i * 8 ) , ctx - > curr [ i ] ) ;
if ( err )
break ;
}
if ( unlikely ( err ) )
pr_err_ratelimited ( " Failed to update LCD display: %d \n " , err ) ;
}
static struct img_ascii_lcd_config malta_config = {
. num_chars = 8 ,
. external_regmap = true ,
. update = malta_update ,
} ;
/*
* MIPS SEAD3 development board
*/
enum {
SEAD3_REG_LCD_CTRL = 0x00 ,
# define SEAD3_REG_LCD_CTRL_SETDRAM BIT(7)
SEAD3_REG_LCD_DATA = 0x08 ,
SEAD3_REG_CPLD_STATUS = 0x10 ,
# define SEAD3_REG_CPLD_STATUS_BUSY BIT(0)
SEAD3_REG_CPLD_DATA = 0x18 ,
# define SEAD3_REG_CPLD_DATA_BUSY BIT(7)
} ;
static int sead3_wait_sm_idle ( struct img_ascii_lcd_ctx * ctx )
{
unsigned int status ;
int err ;
do {
err = regmap_read ( ctx - > regmap ,
ctx - > offset + SEAD3_REG_CPLD_STATUS ,
& status ) ;
if ( err )
return err ;
} while ( status & SEAD3_REG_CPLD_STATUS_BUSY ) ;
return 0 ;
}
static int sead3_wait_lcd_idle ( struct img_ascii_lcd_ctx * ctx )
{
unsigned int cpld_data ;
int err ;
err = sead3_wait_sm_idle ( ctx ) ;
if ( err )
return err ;
do {
err = regmap_read ( ctx - > regmap ,
ctx - > offset + SEAD3_REG_LCD_CTRL ,
& cpld_data ) ;
if ( err )
return err ;
err = sead3_wait_sm_idle ( ctx ) ;
if ( err )
return err ;
err = regmap_read ( ctx - > regmap ,
ctx - > offset + SEAD3_REG_CPLD_DATA ,
& cpld_data ) ;
if ( err )
return err ;
} while ( cpld_data & SEAD3_REG_CPLD_DATA_BUSY ) ;
return 0 ;
}
2021-10-19 16:45:05 +02:00
static void sead3_update ( struct linedisp * linedisp )
2016-08-26 15:17:49 +01:00
{
2021-10-19 16:45:05 +02:00
struct img_ascii_lcd_ctx * ctx =
container_of ( linedisp , struct img_ascii_lcd_ctx , linedisp ) ;
2016-08-26 15:17:49 +01:00
unsigned int i ;
2018-02-19 16:47:52 +01:00
int err = 0 ;
2016-08-26 15:17:49 +01:00
2021-10-19 16:45:05 +02:00
for ( i = 0 ; i < linedisp - > num_chars ; i + + ) {
2016-08-26 15:17:49 +01:00
err = sead3_wait_lcd_idle ( ctx ) ;
if ( err )
break ;
err = regmap_write ( ctx - > regmap ,
ctx - > offset + SEAD3_REG_LCD_CTRL ,
SEAD3_REG_LCD_CTRL_SETDRAM | i ) ;
if ( err )
break ;
err = sead3_wait_lcd_idle ( ctx ) ;
if ( err )
break ;
err = regmap_write ( ctx - > regmap ,
ctx - > offset + SEAD3_REG_LCD_DATA ,
ctx - > curr [ i ] ) ;
if ( err )
break ;
}
if ( unlikely ( err ) )
pr_err_ratelimited ( " Failed to update LCD display: %d \n " , err ) ;
}
static struct img_ascii_lcd_config sead3_config = {
. num_chars = 16 ,
. external_regmap = true ,
. update = sead3_update ,
} ;
static const struct of_device_id img_ascii_lcd_matches [ ] = {
{ . compatible = " img,boston-lcd " , . data = & boston_config } ,
{ . compatible = " mti,malta-lcd " , . data = & malta_config } ,
{ . compatible = " mti,sead3-lcd " , . data = & sead3_config } ,
2017-02-19 16:33:35 -08:00
{ /* sentinel */ }
2016-08-26 15:17:49 +01:00
} ;
2017-03-29 09:37:59 -04:00
MODULE_DEVICE_TABLE ( of , img_ascii_lcd_matches ) ;
2016-08-26 15:17:49 +01:00
/**
* img_ascii_lcd_probe ( ) - probe an LCD display device
* @ pdev : the LCD platform device
*
* Probe an LCD display device , ensuring that we have the required resources in
* order to access the LCD & setting up private data as well as sysfs files .
*
* Return : 0 on success , else - ERRNO
*/
static int img_ascii_lcd_probe ( struct platform_device * pdev )
{
const struct of_device_id * match ;
const struct img_ascii_lcd_config * cfg ;
2021-10-19 16:45:03 +02:00
struct device * dev = & pdev - > dev ;
2016-08-26 15:17:49 +01:00
struct img_ascii_lcd_ctx * ctx ;
int err ;
2021-10-19 16:45:03 +02:00
match = of_match_device ( img_ascii_lcd_matches , dev ) ;
2016-08-26 15:17:49 +01:00
if ( ! match )
return - ENODEV ;
cfg = match - > data ;
2021-10-19 16:45:03 +02:00
ctx = devm_kzalloc ( dev , sizeof ( * ctx ) + cfg - > num_chars , GFP_KERNEL ) ;
2016-08-26 15:17:49 +01:00
if ( ! ctx )
return - ENOMEM ;
if ( cfg - > external_regmap ) {
2021-10-19 16:45:03 +02:00
ctx - > regmap = syscon_node_to_regmap ( dev - > parent - > of_node ) ;
2016-08-26 15:17:49 +01:00
if ( IS_ERR ( ctx - > regmap ) )
return PTR_ERR ( ctx - > regmap ) ;
2021-10-19 16:45:03 +02:00
if ( of_property_read_u32 ( dev - > of_node , " offset " , & ctx - > offset ) )
2016-08-26 15:17:49 +01:00
return - EINVAL ;
} else {
2019-12-29 07:04:19 +00:00
ctx - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2016-08-26 15:17:49 +01:00
if ( IS_ERR ( ctx - > base ) )
return PTR_ERR ( ctx - > base ) ;
}
2021-10-19 16:45:05 +02:00
err = linedisp_register ( & ctx - > linedisp , dev , cfg - > num_chars , ctx - > curr ,
cfg - > update ) ;
2016-08-26 15:17:49 +01:00
if ( err )
2021-10-19 16:45:05 +02:00
return err ;
2016-08-26 15:17:49 +01:00
2021-10-19 16:45:05 +02:00
/* for backwards compatibility */
err = compat_only_sysfs_link_entry_to_kobj ( & dev - > kobj ,
& ctx - > linedisp . dev . kobj ,
" message " , NULL ) ;
2016-08-26 15:17:49 +01:00
if ( err )
2021-10-19 16:45:05 +02:00
goto err_unregister ;
2016-08-26 15:17:49 +01:00
2021-10-19 16:45:05 +02:00
platform_set_drvdata ( pdev , ctx ) ;
2016-08-26 15:17:49 +01:00
return 0 ;
2021-10-19 16:45:05 +02:00
err_unregister :
linedisp_unregister ( & ctx - > linedisp ) ;
2016-08-26 15:17:49 +01:00
return err ;
}
/**
* img_ascii_lcd_remove ( ) - remove an LCD display device
* @ pdev : the LCD platform device
*
* Remove an LCD display device , freeing private resources & ensuring that the
* driver stops using the LCD display registers .
*
* Return : 0
*/
static int img_ascii_lcd_remove ( struct platform_device * pdev )
{
struct img_ascii_lcd_ctx * ctx = platform_get_drvdata ( pdev ) ;
2021-10-19 16:45:05 +02:00
sysfs_remove_link ( & pdev - > dev . kobj , " message " ) ;
linedisp_unregister ( & ctx - > linedisp ) ;
2016-08-26 15:17:49 +01:00
return 0 ;
}
static struct platform_driver img_ascii_lcd_driver = {
. driver = {
. name = " img-ascii-lcd " ,
. of_match_table = img_ascii_lcd_matches ,
} ,
. probe = img_ascii_lcd_probe ,
. remove = img_ascii_lcd_remove ,
} ;
module_platform_driver ( img_ascii_lcd_driver ) ;
2018-01-10 17:41:10 +01:00
MODULE_DESCRIPTION ( " Imagination Technologies ASCII LCD Display " ) ;
MODULE_AUTHOR ( " Paul Burton <paul.burton@mips.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;