mirror of
https://gitlab.rtems.org/rtems/rtos/rtems.git
synced 2026-09-20 21:56:06 +08:00
489 lines
13 KiB
C
489 lines
13 KiB
C
/* SPDX-License-Identifier: BSD-2-Clause */
|
|
|
|
/* GRGPIO GPIO Driver interface.
|
|
*
|
|
* COPYRIGHT (c) 2009.
|
|
* Cobham Gaisler AB.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <bsp.h>
|
|
#include <rtems/libio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <rtems/bspIo.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include <drvmgr/drvmgr.h>
|
|
#include <grlib/ambapp_bus.h>
|
|
#include <grlib/grgpio.h>
|
|
#include <grlib/gpiolib.h>
|
|
#include <grlib/ambapp.h>
|
|
#include <grlib/grlib.h>
|
|
#include <grlib/grlib_impl.h>
|
|
|
|
/*#define DEBUG 1*/
|
|
|
|
#ifdef DEBUG
|
|
#define DBG( x... ) printk( x )
|
|
#define STATIC
|
|
#else
|
|
#define DBG( x... )
|
|
#define STATIC static
|
|
#endif
|
|
|
|
struct grgpio_isr {
|
|
drvmgr_isr isr;
|
|
void *arg;
|
|
};
|
|
|
|
struct grgpio_priv {
|
|
struct drvmgr_dev *dev;
|
|
struct grgpio_regs *regs;
|
|
int irq;
|
|
int minor;
|
|
|
|
/* Driver implementation */
|
|
int port_cnt;
|
|
unsigned char port_handles[ 32 ];
|
|
struct grgpio_isr isrs[ 32 ];
|
|
struct gpiolib_drv gpiolib_desc;
|
|
unsigned int bypass;
|
|
unsigned int imask;
|
|
};
|
|
|
|
/******************* Driver Manager Part ***********************/
|
|
|
|
int grgpio_device_init( struct grgpio_priv *priv );
|
|
|
|
int grgpio_init1( struct drvmgr_dev *dev );
|
|
int grgpio_init2( struct drvmgr_dev *dev );
|
|
|
|
struct drvmgr_drv_ops grgpio_ops =
|
|
{ .init = { grgpio_init1, NULL, NULL, NULL }, .remove = NULL, .info = NULL };
|
|
|
|
struct amba_dev_id grgpio_ids[] = {
|
|
{ VENDOR_GAISLER, GAISLER_GPIO },
|
|
{ 0, 0 } /* Mark end of table */
|
|
};
|
|
|
|
struct amba_drv_info grgpio_drv_info = {
|
|
{
|
|
DRVMGR_OBJ_DRV, /* Driver */
|
|
NULL, /* Next driver */
|
|
NULL, /* Device list */
|
|
DRIVER_AMBAPP_GAISLER_GRGPIO_ID, /* Driver ID */
|
|
"GRGPIO_DRV", /* Driver Name */
|
|
DRVMGR_BUS_TYPE_AMBAPP, /* Bus Type */
|
|
&grgpio_ops,
|
|
NULL, /* Funcs */
|
|
0, /* No devices yet */
|
|
0,
|
|
},
|
|
&grgpio_ids[ 0 ]
|
|
};
|
|
|
|
void grgpio_register_drv( void )
|
|
{
|
|
DBG( "Registering GRGPIO driver\n" );
|
|
drvmgr_drv_register( &grgpio_drv_info.general );
|
|
}
|
|
|
|
/* Register GRGPIO pins as quick as possible to the GPIO library,
|
|
* other drivers may depend upon them in INIT LEVEL 2.
|
|
* Note that since IRQ may not be available in init1, it is assumed
|
|
* that the GPIOLibrary does not request IRQ routines until LEVEL 2.
|
|
*/
|
|
int grgpio_init1( struct drvmgr_dev *dev )
|
|
{
|
|
struct grgpio_priv *priv;
|
|
int status, port;
|
|
|
|
DBG( "GRGPIO[%d] on bus %s\n", dev->minor_drv, dev->parent->dev->name );
|
|
|
|
/* This core will not find other cores, but other driver may depend upon
|
|
* the GPIO library to function. So, we set up GPIO right away.
|
|
*/
|
|
|
|
/* Initialize library if not already done */
|
|
status = gpiolib_initialize();
|
|
if ( status < 0 ) {
|
|
return DRVMGR_FAIL;
|
|
}
|
|
|
|
priv = dev->priv = grlib_calloc( 1, sizeof( *priv ) );
|
|
if ( !priv ) {
|
|
return DRVMGR_NOMEM;
|
|
}
|
|
priv->dev = dev;
|
|
|
|
if ( grgpio_device_init( priv ) ) {
|
|
free( dev->priv );
|
|
dev->priv = NULL;
|
|
return DRVMGR_FAIL;
|
|
}
|
|
|
|
/* Register all ports available on this core as GPIO port to
|
|
* upper layer
|
|
*/
|
|
for ( port = 0; port < priv->port_cnt; port++ ) {
|
|
priv->port_handles[ port ] = port;
|
|
gpiolib_drv_register( &priv->gpiolib_desc, &priv->port_handles[ port ] );
|
|
}
|
|
|
|
return DRVMGR_OK;
|
|
}
|
|
|
|
/******************* Driver Implementation ***********************/
|
|
|
|
/* Find port from handle, returns -1 if not found */
|
|
static int grgpio_find_port( void *handle, struct grgpio_priv **priv )
|
|
{
|
|
unsigned char portnr;
|
|
|
|
portnr = *(unsigned char *) handle;
|
|
if ( portnr > 31 ) {
|
|
return -1;
|
|
}
|
|
*priv = (struct grgpio_priv
|
|
*) ( ( (uintptr_t) handle - portnr * sizeof( unsigned char ) ) -
|
|
offsetof( struct grgpio_priv, port_handles ) );
|
|
return portnr;
|
|
}
|
|
|
|
static int grgpio_gpiolib_open( void *handle )
|
|
{
|
|
struct grgpio_priv *priv;
|
|
int portnr;
|
|
|
|
portnr = grgpio_find_port( handle, &priv );
|
|
if ( portnr < 0 ) {
|
|
DBG( "GRGPIO: FAILED OPENING HANDLE 0x%08x\n", handle );
|
|
return -1;
|
|
}
|
|
DBG( "GRGPIO[0x%08x][%d]: OPENING\n", priv->regs, portnr );
|
|
|
|
/* Open the device, nothing to be done... */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int grgpio_grpiolib_config( void *handle, struct gpiolib_config *cfg )
|
|
{
|
|
struct grgpio_priv *priv;
|
|
int portnr;
|
|
unsigned int mask;
|
|
|
|
portnr = grgpio_find_port( handle, &priv );
|
|
if ( portnr < 0 ) {
|
|
return -1;
|
|
}
|
|
DBG( "GRGPIO[0x%08x][%d]: CONFIG\n", priv->regs, portnr );
|
|
|
|
/* Configure the device. And check that operation is supported,
|
|
* not all I/O Pins have IRQ support.
|
|
*/
|
|
mask = ( 1 << portnr );
|
|
|
|
/* Return error when IRQ not supported by this I/O Line and it
|
|
* is beeing enabled by user.
|
|
*/
|
|
if ( ( ( mask & priv->imask ) == 0 ) && cfg->mask ) {
|
|
return -1;
|
|
}
|
|
|
|
priv->regs->imask &= ~mask; /* Disable interrupt temporarily */
|
|
|
|
/* Configure settings before enabling interrupt */
|
|
priv->regs->ipol = ( priv->regs->ipol & ~mask ) |
|
|
( cfg->irq_polarity ? mask : 0 );
|
|
priv->regs->iedge = ( priv->regs->iedge & ~mask ) |
|
|
( cfg->irq_level ? 0 : mask );
|
|
priv->regs->imask |= cfg->mask ? mask : 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int grgpio_grpiolib_get( void *handle, int *inval )
|
|
{
|
|
struct grgpio_priv *priv;
|
|
int portnr;
|
|
|
|
portnr = grgpio_find_port( handle, &priv );
|
|
if ( portnr < 0 ) {
|
|
return -1;
|
|
}
|
|
DBG( "GRGPIO[0x%08x][%d]: GET\n", priv->regs, portnr );
|
|
|
|
/* Get current status of the port */
|
|
if ( inval ) {
|
|
*inval = ( priv->regs->data >> portnr ) & 0x1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int grgpio_grpiolib_irq_opts( void *handle, unsigned int options )
|
|
{
|
|
struct grgpio_priv *priv;
|
|
int portnr;
|
|
drvmgr_isr isr;
|
|
void *arg;
|
|
|
|
portnr = grgpio_find_port( handle, &priv );
|
|
if ( portnr < 0 ) {
|
|
return -1;
|
|
}
|
|
DBG( "GRGPIO[0x%08x][%d]: IRQ OPTS 0x%x\n", priv->regs, portnr, options );
|
|
|
|
if ( options & GPIOLIB_IRQ_FORCE ) {
|
|
return -1;
|
|
}
|
|
|
|
isr = priv->isrs[ portnr ].isr;
|
|
arg = priv->isrs[ portnr ].arg;
|
|
|
|
if ( options & GPIOLIB_IRQ_DISABLE ) {
|
|
/* Disable interrupt at interrupt controller */
|
|
if ( drvmgr_interrupt_unregister( priv->dev, portnr, isr, arg ) ) {
|
|
return -1;
|
|
}
|
|
}
|
|
if ( options & GPIOLIB_IRQ_CLEAR ) {
|
|
/* Clear interrupt at interrupt controller */
|
|
if ( drvmgr_interrupt_clear( priv->dev, portnr ) ) {
|
|
return -1;
|
|
}
|
|
}
|
|
if ( options & GPIOLIB_IRQ_ENABLE ) {
|
|
/* Enable interrupt at interrupt controller */
|
|
if ( drvmgr_interrupt_register( priv->dev, portnr, "grgpio", isr, arg ) ) {
|
|
return -1;
|
|
}
|
|
}
|
|
if ( options & GPIOLIB_IRQ_MASK ) {
|
|
/* Mask (disable) interrupt at interrupt controller */
|
|
if ( drvmgr_interrupt_mask( priv->dev, portnr ) ) {
|
|
return -1;
|
|
}
|
|
}
|
|
if ( options & GPIOLIB_IRQ_UNMASK ) {
|
|
/* Unmask (enable) interrupt at interrupt controller */
|
|
if ( drvmgr_interrupt_unmask( priv->dev, portnr ) ) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int grgpio_grpiolib_irq_register( void *handle, void *func, void *arg )
|
|
{
|
|
struct grgpio_priv *priv;
|
|
int portnr;
|
|
|
|
portnr = grgpio_find_port( handle, &priv );
|
|
if ( portnr < 0 ) {
|
|
DBG( "GRGPIO: FAILED OPENING HANDLE 0x%08x\n", handle );
|
|
return -1;
|
|
}
|
|
DBG( "GRGPIO: OPENING %d at [0x%08x]\n", portnr, priv->regs );
|
|
|
|
/* Since the user doesn't provide the ISR and argument, we must... */
|
|
priv->isrs[ portnr ].isr = func;
|
|
priv->isrs[ portnr ].arg = arg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int grgpio_grpiolib_set( void *handle, int dir, int outval )
|
|
{
|
|
struct grgpio_priv *priv;
|
|
int portnr;
|
|
unsigned int mask;
|
|
|
|
portnr = grgpio_find_port( handle, &priv );
|
|
if ( portnr < 0 ) {
|
|
DBG( "GRGPIO: FAILED OPENING HANDLE 0x%08x\n", handle );
|
|
return -1;
|
|
}
|
|
DBG( "GRGPIO: OPENING %d at [0x%08x]\n", portnr, priv->regs );
|
|
|
|
/* Set Direction and Output */
|
|
mask = 1 << portnr;
|
|
priv->regs->dir = ( priv->regs->dir & ~mask ) | ( dir ? mask : 0 );
|
|
priv->regs->output = ( priv->regs->output & ~mask ) | ( outval ? mask : 0 );
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int grgpio_gpiolib_show( void *handle )
|
|
{
|
|
struct grgpio_priv *priv;
|
|
int portnr, i, regs[ 7 ];
|
|
volatile uint32_t *reg;
|
|
|
|
portnr = grgpio_find_port( handle, &priv );
|
|
if ( portnr < 0 ) {
|
|
DBG( "GRGPIO: FAILED SHOWING HANDLE 0x%08x\n", handle );
|
|
return -1;
|
|
}
|
|
for ( i = 0, reg = &priv->regs->data; i < 7; i++, reg++ ) {
|
|
regs[ i ] = ( *reg >> portnr ) & 1;
|
|
}
|
|
printf(
|
|
"GRGPIO[%p] PORT[%d]: IN/OUT/DIR: [%d,%d,%d], MASK/POL/EDGE: [%d,%d,%d], BYPASS: %d\n",
|
|
priv->regs,
|
|
portnr,
|
|
regs[ 0 ],
|
|
regs[ 1 ],
|
|
regs[ 2 ],
|
|
regs[ 3 ],
|
|
regs[ 4 ],
|
|
regs[ 5 ],
|
|
regs[ 6 ]
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
static int grgpio_gpiolib_get_info( void *handle, struct gpiolib_info *pinfo )
|
|
{
|
|
struct grgpio_priv *priv;
|
|
int portnr;
|
|
char prefix[ 48 ];
|
|
struct drvmgr_dev *dev;
|
|
|
|
if ( !pinfo ) {
|
|
return -1;
|
|
}
|
|
|
|
portnr = grgpio_find_port( handle, &priv );
|
|
if ( portnr < 0 ) {
|
|
DBG( "GRGPIO: FAILED GET_INFO HANDLE 0x%08x\n", handle );
|
|
return -1;
|
|
}
|
|
|
|
/* Get Filesystem name prefix */
|
|
dev = priv->dev;
|
|
prefix[ 0 ] = '\0';
|
|
if ( drvmgr_get_dev_prefix( dev, prefix ) ) {
|
|
/* Failed to get prefix, make sure of a unique FS name
|
|
* by using the driver minor.
|
|
*/
|
|
snprintf( pinfo->devName, 80, "/dev/grgpio%d/%d", dev->minor_drv, portnr );
|
|
} else {
|
|
/* Got special prefix, this means we have a bus prefix
|
|
* And we should use our "bus minor"
|
|
*/
|
|
snprintf(
|
|
pinfo->devName,
|
|
80,
|
|
"/dev/%sgrgpio%d/%d",
|
|
prefix,
|
|
dev->minor_bus,
|
|
portnr
|
|
);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct gpiolib_drv_ops grgpio_gpiolib_ops = {
|
|
.config = grgpio_grpiolib_config,
|
|
.get = grgpio_grpiolib_get,
|
|
.irq_opts = grgpio_grpiolib_irq_opts,
|
|
.irq_register = grgpio_grpiolib_irq_register,
|
|
.open = grgpio_gpiolib_open,
|
|
.set = grgpio_grpiolib_set,
|
|
.show = grgpio_gpiolib_show,
|
|
.get_info = grgpio_gpiolib_get_info,
|
|
};
|
|
|
|
int grgpio_device_init( struct grgpio_priv *priv )
|
|
{
|
|
struct amba_dev_info *ambadev;
|
|
struct ambapp_core *pnpinfo;
|
|
union drvmgr_key_value *value;
|
|
unsigned int mask;
|
|
int port_cnt;
|
|
|
|
/* Get device information from AMBA PnP information */
|
|
ambadev = (struct amba_dev_info *) priv->dev->businfo;
|
|
if ( ambadev == NULL ) {
|
|
return -1;
|
|
}
|
|
pnpinfo = &ambadev->info;
|
|
priv->irq = pnpinfo->irq;
|
|
priv->regs = (struct grgpio_regs *) (uintptr_t) pnpinfo->apb_slv->start;
|
|
|
|
DBG( "GRGPIO: 0x%08x irq %d\n", (unsigned int) priv->regs, priv->irq );
|
|
|
|
/* Mask all Interrupts */
|
|
priv->regs->imask = 0;
|
|
|
|
/* Make IRQ Rising edge triggered default */
|
|
priv->regs->ipol = 0xfffffffe;
|
|
priv->regs->iedge = 0xfffffffe;
|
|
|
|
/* Read what I/O lines have IRQ support */
|
|
priv->imask = priv->regs->ipol;
|
|
|
|
/* Let the user configure the port count, this might be needed
|
|
* when the GPIO lines must not be changed (assigned during bootup)
|
|
*/
|
|
value = drvmgr_dev_key_get( priv->dev, "nBits", DRVMGR_KT_INT );
|
|
if ( value ) {
|
|
priv->port_cnt = value->i;
|
|
} else {
|
|
/* Auto detect number of GPIO ports */
|
|
priv->regs->dir = 0;
|
|
priv->regs->output = 0xffffffff;
|
|
mask = priv->regs->output;
|
|
priv->regs->output = 0;
|
|
|
|
for ( port_cnt = 0; port_cnt < 32; port_cnt++ ) {
|
|
if ( ( mask & ( 1 << port_cnt ) ) == 0 ) {
|
|
break;
|
|
}
|
|
}
|
|
priv->port_cnt = port_cnt;
|
|
}
|
|
|
|
/* Let the user configure the BYPASS register, this might be needed
|
|
* to select which cores can do I/O on a pin.
|
|
*/
|
|
value = drvmgr_dev_key_get( priv->dev, "bypass", DRVMGR_KT_INT );
|
|
if ( value ) {
|
|
priv->bypass = value->i;
|
|
} else {
|
|
priv->bypass = 0;
|
|
}
|
|
priv->regs->bypass = priv->bypass;
|
|
|
|
/* Prepare GPIOLIB layer */
|
|
priv->gpiolib_desc.ops = &grgpio_gpiolib_ops;
|
|
|
|
return 0;
|
|
}
|