2018-07-10 11:21:22 -03:00
// SPDX-License-Identifier: GPL-2.0
2010-04-29 10:03:17 +02:00
/*
* Watchdog driver for IMX2 and later processors
*
* Copyright ( C ) 2010 Wolfram Sang , Pengutronix e . K . < w . sang @ pengutronix . de >
2014-01-13 19:58:34 +08:00
* Copyright ( C ) 2014 Freescale Semiconductor , Inc .
2010-04-29 10:03:17 +02:00
*
* some parts adapted by similar drivers from Darius Augulis and Vladimir
* Zapolskiy , additional improvements by Wim Van Sebroeck .
*
* NOTE : MX1 has a slightly different Watchdog than MX2 and later :
*
* MX1 : MX2 + :
* - - - - - - - - -
* Registers : 32 - bit 16 - bit
* Stopable timer : Yes No
* Need to enable clk : No Yes
* Halt on suspend : Manual Can be automatic
*/
2014-04-04 09:33:24 +08:00
# include <linux/clk.h>
2014-09-12 15:24:36 +08:00
# include <linux/delay.h>
2010-04-29 10:03:17 +02:00
# include <linux/init.h>
2016-10-07 15:41:39 +03:00
# include <linux/interrupt.h>
2014-04-04 09:33:24 +08:00
# include <linux/io.h>
2010-04-29 10:03:17 +02:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
2014-06-03 10:45:14 +08:00
# include <linux/of_address.h>
2010-04-29 10:03:17 +02:00
# include <linux/platform_device.h>
2014-04-04 09:33:25 +08:00
# include <linux/regmap.h>
2014-04-04 09:33:24 +08:00
# include <linux/watchdog.h>
2010-04-29 10:03:17 +02:00
# define DRIVER_NAME "imx2-wdt"
# define IMX2_WDT_WCR 0x00 /* Control Register */
# define IMX2_WDT_WCR_WT (0xFF << 8) /* -> Watchdog Timeout Field */
2016-08-31 14:52:49 +03:00
# define IMX2_WDT_WCR_WDA BIT(5) /* -> External Reset WDOG_B */
# define IMX2_WDT_WCR_SRS BIT(4) /* -> Software Reset Signal */
# define IMX2_WDT_WCR_WRE BIT(3) /* -> WDOG Reset Enable */
# define IMX2_WDT_WCR_WDE BIT(2) /* -> Watchdog Enable */
# define IMX2_WDT_WCR_WDZST BIT(0) /* -> Watchdog timer Suspend */
2010-04-29 10:03:17 +02:00
# define IMX2_WDT_WSR 0x02 /* Service Register */
# define IMX2_WDT_SEQ1 0x5555 /* -> service sequence 1 */
# define IMX2_WDT_SEQ2 0xAAAA /* -> service sequence 2 */
2012-02-16 12:17:45 +00:00
# define IMX2_WDT_WRSR 0x04 /* Reset Status Register */
2016-08-31 14:52:49 +03:00
# define IMX2_WDT_WRSR_TOUT BIT(1) /* -> Reset due to Timeout */
2012-02-16 12:17:45 +00:00
2016-10-07 15:41:39 +03:00
# define IMX2_WDT_WICR 0x06 /* Interrupt Control Register */
# define IMX2_WDT_WICR_WIE BIT(15) /* -> Interrupt Enable */
# define IMX2_WDT_WICR_WTIS BIT(14) /* -> Interrupt Status */
# define IMX2_WDT_WICR_WICT 0xFF /* -> Interrupt Count Timeout */
2014-09-08 09:14:07 +02:00
# define IMX2_WDT_WMCR 0x08 /* Misc Register */
2019-08-12 15:13:56 +02:00
# define IMX2_WDT_MAX_TIME 128U
2010-04-29 10:03:17 +02:00
# define IMX2_WDT_DEFAULT_TIME 60 /* in seconds */
# define WDOG_SEC_TO_COUNT(s) ((s * 2 - 1) << 8)
2014-04-11 08:57:14 +02:00
struct imx2_wdt_device {
2010-04-29 10:03:17 +02:00
struct clk * clk ;
2014-04-04 09:33:25 +08:00
struct regmap * regmap ;
2014-04-11 08:57:14 +02:00
struct watchdog_device wdog ;
2016-04-01 08:16:43 -07:00
bool ext_reset ;
2014-04-11 08:57:14 +02:00
} ;
2010-04-29 10:03:17 +02:00
2012-03-05 16:51:11 +01:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2010-04-29 10:03:17 +02:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2018-02-08 14:11:08 +01:00
static unsigned timeout ;
2010-04-29 10:03:17 +02:00
module_param ( timeout , uint , 0 ) ;
MODULE_PARM_DESC ( timeout , " Watchdog timeout in seconds (default= "
__MODULE_STRING ( IMX2_WDT_DEFAULT_TIME ) " ) " ) ;
static const struct watchdog_info imx2_wdt_info = {
. identity = " imx2+ watchdog " ,
. options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE ,
} ;
2016-10-07 15:41:39 +03:00
static const struct watchdog_info imx2_wdt_pretimeout_info = {
. identity = " imx2+ watchdog " ,
. options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
WDIOF_PRETIMEOUT ,
} ;
2016-02-26 17:32:49 -08:00
static int imx2_wdt_restart ( struct watchdog_device * wdog , unsigned long action ,
void * data )
2014-09-12 15:24:36 +08:00
{
2015-11-16 12:28:04 -05:00
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
2014-09-12 15:24:36 +08:00
unsigned int wcr_enable = IMX2_WDT_WCR_WDE ;
2015-11-16 12:28:04 -05:00
2016-04-01 08:16:43 -07:00
/* Use internal reset or external - not both */
if ( wdev - > ext_reset )
wcr_enable | = IMX2_WDT_WCR_SRS ; /* do not assert int reset */
else
wcr_enable | = IMX2_WDT_WCR_WDA ; /* do not assert ext-reset */
2014-09-12 15:24:36 +08:00
/* Assert SRS signal */
2015-10-02 00:25:28 -03:00
regmap_write ( wdev - > regmap , IMX2_WDT_WCR , wcr_enable ) ;
2014-09-12 15:24:36 +08:00
/*
* Due to imx6q errata ERR004346 ( WDOG : WDOG SRS bit requires to be
* written twice ) , we add another two writes to ensure there must be at
* least two writes happen in the same one 32 kHz clock period . We save
* the target check here , since the writes shouldn ' t be a huge burden
* for other platforms .
*/
2015-10-02 00:25:28 -03:00
regmap_write ( wdev - > regmap , IMX2_WDT_WCR , wcr_enable ) ;
regmap_write ( wdev - > regmap , IMX2_WDT_WCR , wcr_enable ) ;
2014-09-12 15:24:36 +08:00
/* wait for reset to assert... */
mdelay ( 500 ) ;
2015-11-16 12:28:04 -05:00
return 0 ;
2014-09-12 15:24:36 +08:00
}
2014-04-11 08:57:14 +02:00
static inline void imx2_wdt_setup ( struct watchdog_device * wdog )
2010-04-29 10:03:17 +02:00
{
2014-04-11 08:57:14 +02:00
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
2014-04-04 09:33:25 +08:00
u32 val ;
2014-04-11 08:57:14 +02:00
regmap_read ( wdev - > regmap , IMX2_WDT_WCR , & val ) ;
2010-04-29 10:03:17 +02:00
2014-01-13 19:58:34 +08:00
/* Suspend timer in low power mode, write once-only */
val | = IMX2_WDT_WCR_WDZST ;
2010-04-29 10:03:17 +02:00
/* Strip the old watchdog Time-Out value */
val & = ~ IMX2_WDT_WCR_WT ;
2016-04-01 08:16:43 -07:00
/* Generate internal chip-level reset if WDOG times out */
if ( ! wdev - > ext_reset )
val & = ~ IMX2_WDT_WCR_WRE ;
/* Or if external-reset assert WDOG_B reset only on time-out */
else
val | = IMX2_WDT_WCR_WRE ;
2010-04-29 10:03:17 +02:00
/* Keep Watchdog Disabled */
val & = ~ IMX2_WDT_WCR_WDE ;
/* Set the watchdog's Time-Out value */
2014-04-11 08:57:14 +02:00
val | = WDOG_SEC_TO_COUNT ( wdog - > timeout ) ;
2010-04-29 10:03:17 +02:00
2014-04-11 08:57:14 +02:00
regmap_write ( wdev - > regmap , IMX2_WDT_WCR , val ) ;
2010-04-29 10:03:17 +02:00
/* enable the watchdog */
val | = IMX2_WDT_WCR_WDE ;
2014-04-11 08:57:14 +02:00
regmap_write ( wdev - > regmap , IMX2_WDT_WCR , val ) ;
2010-04-29 10:03:17 +02:00
}
2014-04-11 08:57:14 +02:00
static inline bool imx2_wdt_is_running ( struct imx2_wdt_device * wdev )
2010-04-29 10:03:17 +02:00
{
2014-04-11 08:57:14 +02:00
u32 val ;
2010-04-29 10:03:17 +02:00
2014-04-11 08:57:14 +02:00
regmap_read ( wdev - > regmap , IMX2_WDT_WCR , & val ) ;
return val & IMX2_WDT_WCR_WDE ;
2010-04-29 10:03:17 +02:00
}
2014-04-11 08:57:14 +02:00
static int imx2_wdt_ping ( struct watchdog_device * wdog )
2010-04-29 10:03:17 +02:00
{
2014-04-11 08:57:14 +02:00
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
2010-04-29 10:03:17 +02:00
2014-04-11 08:57:14 +02:00
regmap_write ( wdev - > regmap , IMX2_WDT_WSR , IMX2_WDT_SEQ1 ) ;
regmap_write ( wdev - > regmap , IMX2_WDT_WSR , IMX2_WDT_SEQ2 ) ;
return 0 ;
2010-04-29 10:03:17 +02:00
}
2018-01-01 18:26:47 +01:00
static void __imx2_wdt_set_timeout ( struct watchdog_device * wdog ,
unsigned int new_timeout )
2010-04-29 10:03:17 +02:00
{
2014-04-11 08:57:14 +02:00
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
regmap_update_bits ( wdev - > regmap , IMX2_WDT_WCR , IMX2_WDT_WCR_WT ,
2014-04-04 09:33:25 +08:00
WDOG_SEC_TO_COUNT ( new_timeout ) ) ;
2018-01-01 18:26:47 +01:00
}
static int imx2_wdt_set_timeout ( struct watchdog_device * wdog ,
unsigned int new_timeout )
{
2019-04-08 21:25:54 +02:00
unsigned int actual ;
2018-01-01 18:26:47 +01:00
2019-08-12 15:13:56 +02:00
actual = min ( new_timeout , IMX2_WDT_MAX_TIME ) ;
2019-04-08 21:25:54 +02:00
__imx2_wdt_set_timeout ( wdog , actual ) ;
2018-01-01 18:26:47 +01:00
wdog - > timeout = new_timeout ;
2014-04-11 08:57:14 +02:00
return 0 ;
2010-04-29 10:03:17 +02:00
}
2016-10-07 15:41:39 +03:00
static int imx2_wdt_set_pretimeout ( struct watchdog_device * wdog ,
unsigned int new_pretimeout )
{
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
if ( new_pretimeout > = IMX2_WDT_MAX_TIME )
return - EINVAL ;
wdog - > pretimeout = new_pretimeout ;
regmap_update_bits ( wdev - > regmap , IMX2_WDT_WICR ,
IMX2_WDT_WICR_WIE | IMX2_WDT_WICR_WICT ,
IMX2_WDT_WICR_WIE | ( new_pretimeout < < 1 ) ) ;
return 0 ;
}
static irqreturn_t imx2_wdt_isr ( int irq , void * wdog_arg )
{
struct watchdog_device * wdog = wdog_arg ;
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
regmap_write_bits ( wdev - > regmap , IMX2_WDT_WICR ,
IMX2_WDT_WICR_WTIS , IMX2_WDT_WICR_WTIS ) ;
watchdog_notify_pretimeout ( wdog ) ;
return IRQ_HANDLED ;
}
2014-04-11 08:57:14 +02:00
static int imx2_wdt_start ( struct watchdog_device * wdog )
2010-04-29 10:03:17 +02:00
{
2014-04-11 08:57:14 +02:00
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
2010-04-29 10:03:17 +02:00
2016-02-28 13:12:20 -08:00
if ( imx2_wdt_is_running ( wdev ) )
2014-04-11 08:57:14 +02:00
imx2_wdt_set_timeout ( wdog , wdog - > timeout ) ;
2016-02-28 13:12:20 -08:00
else
2014-04-11 08:57:14 +02:00
imx2_wdt_setup ( wdog ) ;
2016-02-28 13:12:20 -08:00
set_bit ( WDOG_HW_RUNNING , & wdog - > status ) ;
2010-04-29 10:03:17 +02:00
2016-02-28 13:12:20 -08:00
return imx2_wdt_ping ( wdog ) ;
2010-04-29 10:03:17 +02:00
}
2015-01-05 10:09:17 +01:00
static const struct watchdog_ops imx2_wdt_ops = {
2010-04-29 10:03:17 +02:00
. owner = THIS_MODULE ,
2014-04-11 08:57:14 +02:00
. start = imx2_wdt_start ,
. ping = imx2_wdt_ping ,
. set_timeout = imx2_wdt_set_timeout ,
2016-10-07 15:41:39 +03:00
. set_pretimeout = imx2_wdt_set_pretimeout ,
2015-11-16 12:28:04 -05:00
. restart = imx2_wdt_restart ,
2010-04-29 10:03:17 +02:00
} ;
2015-01-05 10:09:17 +01:00
static const struct regmap_config imx2_wdt_regmap_config = {
2014-04-04 09:33:25 +08:00
. reg_bits = 16 ,
. reg_stride = 2 ,
. val_bits = 16 ,
. max_register = 0x8 ,
} ;
2020-02-24 10:51:27 +08:00
static void imx2_wdt_action ( void * data )
{
clk_disable_unprepare ( data ) ;
}
2010-04-29 10:03:17 +02:00
static int __init imx2_wdt_probe ( struct platform_device * pdev )
{
2019-09-24 15:07:08 +08:00
struct device * dev = & pdev - > dev ;
2014-04-11 08:57:14 +02:00
struct imx2_wdt_device * wdev ;
struct watchdog_device * wdog ;
2014-04-04 09:33:25 +08:00
void __iomem * base ;
int ret ;
2014-04-11 08:57:14 +02:00
u32 val ;
2019-09-24 15:07:08 +08:00
wdev = devm_kzalloc ( dev , sizeof ( * wdev ) , GFP_KERNEL ) ;
2014-04-11 08:57:14 +02:00
if ( ! wdev )
return - ENOMEM ;
2010-04-29 10:03:17 +02:00
2019-04-01 05:04:30 +00:00
base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2014-04-04 09:33:25 +08:00
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
2019-09-24 15:07:08 +08:00
wdev - > regmap = devm_regmap_init_mmio_clk ( dev , NULL , base ,
2014-04-11 08:57:14 +02:00
& imx2_wdt_regmap_config ) ;
if ( IS_ERR ( wdev - > regmap ) ) {
2019-09-24 15:07:08 +08:00
dev_err ( dev , " regmap init failed \n " ) ;
2014-04-11 08:57:14 +02:00
return PTR_ERR ( wdev - > regmap ) ;
2014-04-04 09:33:25 +08:00
}
2010-04-29 10:03:17 +02:00
2019-09-24 15:07:08 +08:00
wdev - > clk = devm_clk_get ( dev , NULL ) ;
2014-04-11 08:57:14 +02:00
if ( IS_ERR ( wdev - > clk ) ) {
2019-09-24 15:07:08 +08:00
dev_err ( dev , " can't get Watchdog clock \n " ) ;
2014-04-11 08:57:14 +02:00
return PTR_ERR ( wdev - > clk ) ;
2010-04-29 10:03:17 +02:00
}
2014-04-11 08:57:14 +02:00
wdog = & wdev - > wdog ;
wdog - > info = & imx2_wdt_info ;
wdog - > ops = & imx2_wdt_ops ;
wdog - > min_timeout = 1 ;
2018-02-08 14:11:08 +01:00
wdog - > timeout = IMX2_WDT_DEFAULT_TIME ;
2016-02-28 13:12:20 -08:00
wdog - > max_hw_heartbeat_ms = IMX2_WDT_MAX_TIME * 1000 ;
2019-09-24 15:07:08 +08:00
wdog - > parent = dev ;
2010-04-29 10:03:17 +02:00
2016-10-07 15:41:39 +03:00
ret = platform_get_irq ( pdev , 0 ) ;
if ( ret > 0 )
2019-09-24 15:07:08 +08:00
if ( ! devm_request_irq ( dev , ret , imx2_wdt_isr , 0 ,
dev_name ( dev ) , wdog ) )
2016-10-07 15:41:39 +03:00
wdog - > info = & imx2_wdt_pretimeout_info ;
2015-06-22 01:16:18 -03:00
ret = clk_prepare_enable ( wdev - > clk ) ;
if ( ret )
return ret ;
2010-04-29 10:03:17 +02:00
2020-02-24 10:51:27 +08:00
ret = devm_add_action_or_reset ( dev , imx2_wdt_action , wdev - > clk ) ;
if ( ret )
return ret ;
2014-04-11 08:57:14 +02:00
regmap_read ( wdev - > regmap , IMX2_WDT_WRSR , & val ) ;
wdog - > bootstatus = val & IMX2_WDT_WRSR_TOUT ? WDIOF_CARDRESET : 0 ;
2010-04-29 10:03:17 +02:00
2019-09-24 15:07:08 +08:00
wdev - > ext_reset = of_property_read_bool ( dev - > of_node ,
2016-04-01 08:16:43 -07:00
" fsl,ext-reset-output " ) ;
2014-04-11 08:57:14 +02:00
platform_set_drvdata ( pdev , wdog ) ;
watchdog_set_drvdata ( wdog , wdev ) ;
watchdog_set_nowayout ( wdog , nowayout ) ;
2015-11-16 12:28:04 -05:00
watchdog_set_restart_priority ( wdog , 128 ) ;
2019-09-24 15:07:08 +08:00
watchdog_init_timeout ( wdog , timeout , dev ) ;
2014-04-11 08:57:14 +02:00
2016-02-28 13:12:20 -08:00
if ( imx2_wdt_is_running ( wdev ) ) {
imx2_wdt_set_timeout ( wdog , wdog - > timeout ) ;
set_bit ( WDOG_HW_RUNNING , & wdog - > status ) ;
}
2014-04-11 08:57:14 +02:00
2014-09-08 09:14:07 +02:00
/*
* Disable the watchdog power down counter at boot . Otherwise the power
* down counter will pull down the # WDOG interrupt line for one clock
* cycle .
*/
regmap_write ( wdev - > regmap , IMX2_WDT_WMCR , 0 ) ;
2020-02-24 10:51:27 +08:00
return devm_watchdog_register_device ( dev , wdog ) ;
2010-04-29 10:03:17 +02:00
}
static void imx2_wdt_shutdown ( struct platform_device * pdev )
{
2014-04-11 08:57:14 +02:00
struct watchdog_device * wdog = platform_get_drvdata ( pdev ) ;
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
if ( imx2_wdt_is_running ( wdev ) ) {
/*
2016-02-28 13:12:20 -08:00
* We are running , configure max timeout before reboot
* will take place .
2014-04-11 08:57:14 +02:00
*/
imx2_wdt_set_timeout ( wdog , IMX2_WDT_MAX_TIME ) ;
imx2_wdt_ping ( wdog ) ;
dev_crit ( & pdev - > dev , " Device shutdown: Expect reboot! \n " ) ;
2010-04-29 10:03:17 +02:00
}
}
2014-10-16 11:44:15 +08:00
/* Disable watchdog if it is active or non-active but still running */
2019-09-24 15:07:07 +08:00
static int __maybe_unused imx2_wdt_suspend ( struct device * dev )
2014-09-22 18:00:52 +08:00
{
struct watchdog_device * wdog = dev_get_drvdata ( dev ) ;
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
2014-10-16 11:44:15 +08:00
/* The watchdog IP block is running */
if ( imx2_wdt_is_running ( wdev ) ) {
2018-01-01 18:26:47 +01:00
/*
* Don ' t update wdog - > timeout , we ' ll restore the current value
* during resume .
*/
__imx2_wdt_set_timeout ( wdog , IMX2_WDT_MAX_TIME ) ;
2014-10-16 11:44:15 +08:00
imx2_wdt_ping ( wdog ) ;
}
2014-09-22 18:00:52 +08:00
clk_disable_unprepare ( wdev - > clk ) ;
return 0 ;
}
/* Enable watchdog and configure it if necessary */
2019-09-24 15:07:07 +08:00
static int __maybe_unused imx2_wdt_resume ( struct device * dev )
2014-09-22 18:00:52 +08:00
{
struct watchdog_device * wdog = dev_get_drvdata ( dev ) ;
struct imx2_wdt_device * wdev = watchdog_get_drvdata ( wdog ) ;
2015-06-22 01:16:18 -03:00
int ret ;
2014-09-22 18:00:52 +08:00
2015-06-22 01:16:18 -03:00
ret = clk_prepare_enable ( wdev - > clk ) ;
if ( ret )
return ret ;
2014-09-22 18:00:52 +08:00
if ( watchdog_active ( wdog ) & & ! imx2_wdt_is_running ( wdev ) ) {
2014-10-16 11:44:15 +08:00
/*
* If the watchdog is still active and resumes
* from deep sleep state , need to restart the
* watchdog again .
2014-09-22 18:00:52 +08:00
*/
imx2_wdt_setup ( wdog ) ;
2016-02-28 13:12:20 -08:00
}
if ( imx2_wdt_is_running ( wdev ) ) {
2014-09-22 18:00:52 +08:00
imx2_wdt_set_timeout ( wdog , wdog - > timeout ) ;
imx2_wdt_ping ( wdog ) ;
}
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( imx2_wdt_pm_ops , imx2_wdt_suspend ,
imx2_wdt_resume ) ;
2011-07-18 11:15:21 +08:00
static const struct of_device_id imx2_wdt_dt_ids [ ] = {
{ . compatible = " fsl,imx21-wdt " , } ,
{ /* sentinel */ }
} ;
2013-07-29 09:38:18 +02:00
MODULE_DEVICE_TABLE ( of , imx2_wdt_dt_ids ) ;
2011-07-18 11:15:21 +08:00
2010-04-29 10:03:17 +02:00
static struct platform_driver imx2_wdt_driver = {
. shutdown = imx2_wdt_shutdown ,
. driver = {
. name = DRIVER_NAME ,
2014-09-22 18:00:52 +08:00
. pm = & imx2_wdt_pm_ops ,
2011-07-18 11:15:21 +08:00
. of_match_table = imx2_wdt_dt_ids ,
2010-04-29 10:03:17 +02:00
} ,
} ;
2013-01-09 12:15:27 +01:00
module_platform_driver_probe ( imx2_wdt_driver , imx2_wdt_probe ) ;
2010-04-29 10:03:17 +02:00
MODULE_AUTHOR ( " Wolfram Sang " ) ;
MODULE_DESCRIPTION ( " Watchdog driver for IMX2 and later " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform: " DRIVER_NAME ) ;