2019-06-25 10:57:46 +03:00
// SPDX-License-Identifier: GPL-2.0
//
2020-07-09 21:07:33 +03:00
// Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/
2019-06-25 10:57:46 +03:00
// Author: Vignesh Raghavendra <vigneshr@ti.com>
2020-09-24 11:12:14 +03:00
# include <linux/completion.h>
# include <linux/dma-direction.h>
# include <linux/dma-mapping.h>
# include <linux/dmaengine.h>
2019-06-25 10:57:46 +03:00
# include <linux/err.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mtd/cfi.h>
# include <linux/mtd/hyperbus.h>
# include <linux/mtd/mtd.h>
# include <linux/mux/consumer.h>
# include <linux/of.h>
2020-01-29 23:37:37 +03:00
# include <linux/of_address.h>
2019-06-25 10:57:46 +03:00
# include <linux/platform_device.h>
2020-09-24 11:12:14 +03:00
# include <linux/sched/task_stack.h>
2019-06-25 10:57:46 +03:00
# include <linux/types.h>
# define AM654_HBMC_CALIB_COUNT 25
2020-09-24 11:12:14 +03:00
struct am654_hbmc_device_priv {
struct completion rx_dma_complete ;
phys_addr_t device_base ;
struct hyperbus_ctlr * ctlr ;
struct dma_chan * rx_chan ;
} ;
2019-06-25 10:57:46 +03:00
struct am654_hbmc_priv {
struct hyperbus_ctlr ctlr ;
struct hyperbus_device hbdev ;
struct mux_control * mux_ctrl ;
} ;
static int am654_hbmc_calibrate ( struct hyperbus_device * hbdev )
{
struct map_info * map = & hbdev - > map ;
struct cfi_private cfi ;
int count = AM654_HBMC_CALIB_COUNT ;
int pass_count = 0 ;
int ret ;
cfi . interleave = 1 ;
cfi . device_type = CFI_DEVICETYPE_X16 ;
cfi_send_gen_cmd ( 0xF0 , 0 , 0 , map , & cfi , cfi . device_type , NULL ) ;
cfi_send_gen_cmd ( 0x98 , 0x55 , 0 , map , & cfi , cfi . device_type , NULL ) ;
while ( count - - ) {
ret = cfi_qry_present ( map , 0 , & cfi ) ;
if ( ret )
pass_count + + ;
else
pass_count = 0 ;
if ( pass_count = = 5 )
break ;
}
cfi_qry_mode_off ( 0 , map , & cfi ) ;
return ret ;
}
2020-09-24 11:12:14 +03:00
static void am654_hbmc_dma_callback ( void * param )
{
struct am654_hbmc_device_priv * priv = param ;
complete ( & priv - > rx_dma_complete ) ;
}
static int am654_hbmc_dma_read ( struct am654_hbmc_device_priv * priv , void * to ,
unsigned long from , ssize_t len )
{
enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT ;
struct dma_chan * rx_chan = priv - > rx_chan ;
struct dma_async_tx_descriptor * tx ;
dma_addr_t dma_dst , dma_src ;
dma_cookie_t cookie ;
int ret ;
if ( ! priv - > rx_chan | | ! virt_addr_valid ( to ) | | object_is_on_stack ( to ) )
return - EINVAL ;
dma_dst = dma_map_single ( rx_chan - > device - > dev , to , len , DMA_FROM_DEVICE ) ;
if ( dma_mapping_error ( rx_chan - > device - > dev , dma_dst ) ) {
dev_dbg ( priv - > ctlr - > dev , " DMA mapping failed \n " ) ;
return - EIO ;
}
dma_src = priv - > device_base + from ;
tx = dmaengine_prep_dma_memcpy ( rx_chan , dma_dst , dma_src , len , flags ) ;
if ( ! tx ) {
dev_err ( priv - > ctlr - > dev , " device_prep_dma_memcpy error \n " ) ;
ret = - EIO ;
goto unmap_dma ;
}
reinit_completion ( & priv - > rx_dma_complete ) ;
tx - > callback = am654_hbmc_dma_callback ;
tx - > callback_param = priv ;
cookie = dmaengine_submit ( tx ) ;
ret = dma_submit_error ( cookie ) ;
if ( ret ) {
dev_err ( priv - > ctlr - > dev , " dma_submit_error %d \n " , cookie ) ;
goto unmap_dma ;
}
dma_async_issue_pending ( rx_chan ) ;
if ( ! wait_for_completion_timeout ( & priv - > rx_dma_complete , msecs_to_jiffies ( len + 1000 ) ) ) {
dmaengine_terminate_sync ( rx_chan ) ;
dev_err ( priv - > ctlr - > dev , " DMA wait_for_completion_timeout \n " ) ;
ret = - ETIMEDOUT ;
}
unmap_dma :
dma_unmap_single ( rx_chan - > device - > dev , dma_dst , len , DMA_FROM_DEVICE ) ;
return ret ;
}
static void am654_hbmc_read ( struct hyperbus_device * hbdev , void * to ,
unsigned long from , ssize_t len )
{
struct am654_hbmc_device_priv * priv = hbdev - > priv ;
if ( len < SZ_1K | | am654_hbmc_dma_read ( priv , to , from , len ) )
memcpy_fromio ( to , hbdev - > map . virt + from , len ) ;
}
2019-06-25 10:57:46 +03:00
static const struct hyperbus_ops am654_hbmc_ops = {
. calibrate = am654_hbmc_calibrate ,
2020-09-24 11:12:14 +03:00
. copy_from = am654_hbmc_read ,
2019-06-25 10:57:46 +03:00
} ;
2020-09-24 11:12:14 +03:00
static int am654_hbmc_request_mmap_dma ( struct am654_hbmc_device_priv * priv )
{
struct dma_chan * rx_chan ;
dma_cap_mask_t mask ;
dma_cap_zero ( mask ) ;
dma_cap_set ( DMA_MEMCPY , mask ) ;
rx_chan = dma_request_chan_by_mask ( & mask ) ;
if ( IS_ERR ( rx_chan ) ) {
if ( PTR_ERR ( rx_chan ) = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
dev_dbg ( priv - > ctlr - > dev , " No DMA channel available \n " ) ;
return 0 ;
}
priv - > rx_chan = rx_chan ;
init_completion ( & priv - > rx_dma_complete ) ;
return 0 ;
}
2019-06-25 10:57:46 +03:00
static int am654_hbmc_probe ( struct platform_device * pdev )
{
2020-01-29 23:37:37 +03:00
struct device_node * np = pdev - > dev . of_node ;
2020-09-24 11:12:14 +03:00
struct am654_hbmc_device_priv * dev_priv ;
2019-06-25 10:57:46 +03:00
struct device * dev = & pdev - > dev ;
struct am654_hbmc_priv * priv ;
2020-01-29 23:37:37 +03:00
struct resource res ;
2019-06-25 10:57:46 +03:00
int ret ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
platform_set_drvdata ( pdev , priv ) ;
2020-09-24 11:12:12 +03:00
priv - > hbdev . np = of_get_next_child ( np , NULL ) ;
ret = of_address_to_resource ( priv - > hbdev . np , 0 , & res ) ;
2020-01-29 23:37:37 +03:00
if ( ret )
return ret ;
2019-06-25 10:57:46 +03:00
if ( of_property_read_bool ( dev - > of_node , " mux-controls " ) ) {
struct mux_control * control = devm_mux_control_get ( dev , NULL ) ;
if ( IS_ERR ( control ) )
return PTR_ERR ( control ) ;
ret = mux_control_select ( control , 1 ) ;
if ( ret ) {
dev_err ( dev , " Failed to select HBMC mux \n " ) ;
return ret ;
}
priv - > mux_ctrl = control ;
}
2020-01-29 23:37:37 +03:00
priv - > hbdev . map . size = resource_size ( & res ) ;
priv - > hbdev . map . virt = devm_ioremap_resource ( dev , & res ) ;
if ( IS_ERR ( priv - > hbdev . map . virt ) )
return PTR_ERR ( priv - > hbdev . map . virt ) ;
2019-06-25 10:57:46 +03:00
priv - > ctlr . dev = dev ;
priv - > ctlr . ops = & am654_hbmc_ops ;
priv - > hbdev . ctlr = & priv - > ctlr ;
2020-09-24 11:12:14 +03:00
dev_priv = devm_kzalloc ( dev , sizeof ( * dev_priv ) , GFP_KERNEL ) ;
if ( ! dev_priv ) {
ret = - ENOMEM ;
goto disable_mux ;
}
priv - > hbdev . priv = dev_priv ;
dev_priv - > device_base = res . start ;
dev_priv - > ctlr = & priv - > ctlr ;
ret = am654_hbmc_request_mmap_dma ( dev_priv ) ;
if ( ret )
goto disable_mux ;
2019-06-25 10:57:46 +03:00
ret = hyperbus_register_device ( & priv - > hbdev ) ;
if ( ret ) {
dev_err ( dev , " failed to register controller \n " ) ;
2020-09-24 11:12:14 +03:00
goto release_dma ;
2019-06-25 10:57:46 +03:00
}
return 0 ;
2020-09-24 11:12:14 +03:00
release_dma :
if ( dev_priv - > rx_chan )
dma_release_channel ( dev_priv - > rx_chan ) ;
2020-09-24 11:12:13 +03:00
disable_mux :
2019-06-25 10:57:46 +03:00
if ( priv - > mux_ctrl )
mux_control_deselect ( priv - > mux_ctrl ) ;
return ret ;
}
static int am654_hbmc_remove ( struct platform_device * pdev )
{
struct am654_hbmc_priv * priv = platform_get_drvdata ( pdev ) ;
2020-09-24 11:12:14 +03:00
struct am654_hbmc_device_priv * dev_priv = priv - > hbdev . priv ;
2019-06-25 10:57:46 +03:00
int ret ;
ret = hyperbus_unregister_device ( & priv - > hbdev ) ;
if ( priv - > mux_ctrl )
mux_control_deselect ( priv - > mux_ctrl ) ;
2020-09-24 11:12:14 +03:00
if ( dev_priv - > rx_chan )
dma_release_channel ( dev_priv - > rx_chan ) ;
2019-06-25 10:57:46 +03:00
return ret ;
}
static const struct of_device_id am654_hbmc_dt_ids [ ] = {
{
. compatible = " ti,am654-hbmc " ,
} ,
{ /* end of table */ }
} ;
MODULE_DEVICE_TABLE ( of , am654_hbmc_dt_ids ) ;
static struct platform_driver am654_hbmc_platform_driver = {
. probe = am654_hbmc_probe ,
. remove = am654_hbmc_remove ,
. driver = {
. name = " hbmc-am654 " ,
. of_match_table = am654_hbmc_dt_ids ,
} ,
} ;
module_platform_driver ( am654_hbmc_platform_driver ) ;
MODULE_DESCRIPTION ( " HBMC driver for AM654 SoC " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:hbmc-am654 " ) ;
MODULE_AUTHOR ( " Vignesh Raghavendra <vigneshr@ti.com> " ) ;