2019-05-19 15:08:55 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2011-11-22 18:38:02 +04:00
# include <linux/export.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <asm/addrspace.h>
# include <asm/paccess.h>
# include <asm/gio_device.h>
# include <asm/sgi/gio.h>
# include <asm/sgi/hpc3.h>
# include <asm/sgi/mc.h>
# include <asm/sgi/ip22.h>
static struct bus_type gio_bus_type ;
static struct {
const char * name ;
2013-01-22 15:59:30 +04:00
__u8 id ;
2011-11-22 18:38:02 +04:00
} gio_name_table [ ] = {
{ . name = " SGI Impact " , . id = 0x10 } ,
{ . name = " Phobos G160 " , . id = 0x35 } ,
2014-06-04 14:00:37 +04:00
{ . name = " Phobos G130 " , . id = 0x36 } ,
{ . name = " Phobos G100 " , . id = 0x37 } ,
{ . name = " Set Engineering GFE " , . id = 0x38 } ,
2011-11-22 18:38:02 +04:00
/* fake IDs */
{ . name = " SGI Newport " , . id = 0x7e } ,
{ . name = " SGI GR2/GR3 " , . id = 0x7f } ,
} ;
2013-12-19 19:03:27 +04:00
static void gio_bus_release ( struct device * dev )
{
kfree ( dev ) ;
}
2011-11-22 18:38:02 +04:00
static struct device gio_bus = {
. init_name = " gio " ,
2013-12-19 19:03:27 +04:00
. release = & gio_bus_release ,
2011-11-22 18:38:02 +04:00
} ;
/**
* gio_match_device - Tell if an of_device structure has a matching
* gio_match structure
* @ ids : array of of device match structures to search in
* @ dev : the of device structure to match against
*
* Used by a driver to check whether an of_device present in the
* system is in its list of supported devices .
*/
2020-01-12 19:51:10 +03:00
static const struct gio_device_id *
gio_match_device ( const struct gio_device_id * match ,
const struct gio_device * dev )
2011-11-22 18:38:02 +04:00
{
const struct gio_device_id * ids ;
for ( ids = match ; ids - > id ! = 0xff ; ids + + )
if ( ids - > id = = dev - > id . id )
return ids ;
return NULL ;
}
struct gio_device * gio_dev_get ( struct gio_device * dev )
{
struct device * tmp ;
if ( ! dev )
return NULL ;
tmp = get_device ( & dev - > dev ) ;
if ( tmp )
return to_gio_device ( tmp ) ;
else
return NULL ;
}
EXPORT_SYMBOL_GPL ( gio_dev_get ) ;
void gio_dev_put ( struct gio_device * dev )
{
if ( dev )
put_device ( & dev - > dev ) ;
}
EXPORT_SYMBOL_GPL ( gio_dev_put ) ;
/**
* gio_release_dev - free an gio device structure when all users of it are finished .
* @ dev : device that ' s been disconnected
*
* Will be called only by the device core when all users of this gio device are
* done .
*/
void gio_release_dev ( struct device * dev )
{
struct gio_device * giodev ;
giodev = to_gio_device ( dev ) ;
kfree ( giodev ) ;
}
EXPORT_SYMBOL_GPL ( gio_release_dev ) ;
int gio_device_register ( struct gio_device * giodev )
{
giodev - > dev . bus = & gio_bus_type ;
giodev - > dev . parent = & gio_bus ;
return device_register ( & giodev - > dev ) ;
}
EXPORT_SYMBOL_GPL ( gio_device_register ) ;
void gio_device_unregister ( struct gio_device * giodev )
{
device_unregister ( & giodev - > dev ) ;
}
EXPORT_SYMBOL_GPL ( gio_device_unregister ) ;
static int gio_bus_match ( struct device * dev , struct device_driver * drv )
{
struct gio_device * gio_dev = to_gio_device ( dev ) ;
struct gio_driver * gio_drv = to_gio_driver ( drv ) ;
return gio_match_device ( gio_drv - > id_table , gio_dev ) ! = NULL ;
}
static int gio_device_probe ( struct device * dev )
{
int error = - ENODEV ;
struct gio_driver * drv ;
struct gio_device * gio_dev ;
const struct gio_device_id * match ;
drv = to_gio_driver ( dev - > driver ) ;
gio_dev = to_gio_device ( dev ) ;
if ( ! drv - > probe )
return error ;
gio_dev_get ( gio_dev ) ;
match = gio_match_device ( drv - > id_table , gio_dev ) ;
if ( match )
error = drv - > probe ( gio_dev , match ) ;
if ( error )
gio_dev_put ( gio_dev ) ;
return error ;
}
static int gio_device_remove ( struct device * dev )
{
struct gio_device * gio_dev = to_gio_device ( dev ) ;
struct gio_driver * drv = to_gio_driver ( dev - > driver ) ;
if ( dev - > driver & & drv - > remove )
drv - > remove ( gio_dev ) ;
return 0 ;
}
static void gio_device_shutdown ( struct device * dev )
{
struct gio_device * gio_dev = to_gio_device ( dev ) ;
struct gio_driver * drv = to_gio_driver ( dev - > driver ) ;
if ( dev - > driver & & drv - > shutdown )
drv - > shutdown ( gio_dev ) ;
}
static ssize_t modalias_show ( struct device * dev , struct device_attribute * a ,
char * buf )
{
struct gio_device * gio_dev = to_gio_device ( dev ) ;
int len = snprintf ( buf , PAGE_SIZE , " gio:%x \n " , gio_dev - > id . id ) ;
return ( len > = PAGE_SIZE ) ? ( PAGE_SIZE - 1 ) : len ;
}
2017-06-06 15:16:30 +03:00
static DEVICE_ATTR_RO ( modalias ) ;
2011-11-22 18:38:02 +04:00
static ssize_t name_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct gio_device * giodev ;
giodev = to_gio_device ( dev ) ;
return sprintf ( buf , " %s " , giodev - > name ) ;
}
2017-06-06 15:16:30 +03:00
static DEVICE_ATTR_RO ( name ) ;
2011-11-22 18:38:02 +04:00
static ssize_t id_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct gio_device * giodev ;
giodev = to_gio_device ( dev ) ;
return sprintf ( buf , " %x " , giodev - > id . id ) ;
}
2017-06-06 15:16:30 +03:00
static DEVICE_ATTR_RO ( id ) ;
2011-11-22 18:38:02 +04:00
2017-06-06 15:16:30 +03:00
static struct attribute * gio_dev_attrs [ ] = {
& dev_attr_modalias . attr ,
& dev_attr_name . attr ,
& dev_attr_id . attr ,
NULL ,
2011-11-22 18:38:02 +04:00
} ;
2017-06-06 15:16:30 +03:00
ATTRIBUTE_GROUPS ( gio_dev ) ;
2011-11-22 18:38:02 +04:00
static int gio_device_uevent ( struct device * dev , struct kobj_uevent_env * env )
{
struct gio_device * gio_dev = to_gio_device ( dev ) ;
add_uevent_var ( env , " MODALIAS=gio:%x " , gio_dev - > id . id ) ;
return 0 ;
}
int gio_register_driver ( struct gio_driver * drv )
{
/* initialize common driver fields */
if ( ! drv - > driver . name )
drv - > driver . name = drv - > name ;
if ( ! drv - > driver . owner )
drv - > driver . owner = drv - > owner ;
drv - > driver . bus = & gio_bus_type ;
/* register with core */
return driver_register ( & drv - > driver ) ;
}
EXPORT_SYMBOL_GPL ( gio_register_driver ) ;
void gio_unregister_driver ( struct gio_driver * drv )
{
driver_unregister ( & drv - > driver ) ;
}
EXPORT_SYMBOL_GPL ( gio_unregister_driver ) ;
void gio_set_master ( struct gio_device * dev )
{
u32 tmp = sgimc - > giopar ;
switch ( dev - > slotno ) {
case 0 :
tmp | = SGIMC_GIOPAR_MASTERGFX ;
break ;
case 1 :
tmp | = SGIMC_GIOPAR_MASTEREXP0 ;
break ;
case 2 :
tmp | = SGIMC_GIOPAR_MASTEREXP1 ;
break ;
}
sgimc - > giopar = tmp ;
}
EXPORT_SYMBOL_GPL ( gio_set_master ) ;
void ip22_gio_set_64bit ( int slotno )
{
u32 tmp = sgimc - > giopar ;
switch ( slotno ) {
case 0 :
tmp | = SGIMC_GIOPAR_GFX64 ;
break ;
case 1 :
tmp | = SGIMC_GIOPAR_EXP064 ;
break ;
case 2 :
tmp | = SGIMC_GIOPAR_EXP164 ;
break ;
}
sgimc - > giopar = tmp ;
}
static int ip22_gio_id ( unsigned long addr , u32 * res )
{
u8 tmp8 ;
u8 tmp16 ;
u32 tmp32 ;
u8 * ptr8 ;
u16 * ptr16 ;
u32 * ptr32 ;
ptr32 = ( void * ) CKSEG1ADDR ( addr ) ;
if ( ! get_dbe ( tmp32 , ptr32 ) ) {
/*
* We got no DBE , but this doesn ' t mean anything .
* If GIO is pipelined ( which can ' t be disabled
* for GFX slot ) we don ' t get a DBE , but we see
* the transfer size as data . So we do an 8 bit
* and a 16 bit access and check whether the common
* data matches
*/
ptr8 = ( void * ) CKSEG1ADDR ( addr + 3 ) ;
2014-06-04 14:00:37 +04:00
if ( get_dbe ( tmp8 , ptr8 ) ) {
/*
* 32 bit access worked , but 8 bit doesn ' t
* so we don ' t see phantom reads on
* a pipelined bus , but a real card which
* doesn ' t support 8 bit reads
*/
* res = tmp32 ;
return 1 ;
}
2011-11-22 18:38:02 +04:00
ptr16 = ( void * ) CKSEG1ADDR ( addr + 2 ) ;
get_dbe ( tmp16 , ptr16 ) ;
if ( tmp8 = = ( tmp16 & 0xff ) & &
tmp8 = = ( tmp32 & 0xff ) & &
tmp16 = = ( tmp32 & 0xffff ) ) {
* res = tmp32 ;
return 1 ;
}
}
return 0 ; /* nothing here */
}
# define HQ2_MYSTERY_OFFS 0x6A07C
# define NEWPORT_USTATUS_OFFS 0xF133C
static int ip22_is_gr2 ( unsigned long addr )
{
u32 tmp ;
u32 * ptr ;
/* HQ2 only allows 32bit accesses */
ptr = ( void * ) CKSEG1ADDR ( addr + HQ2_MYSTERY_OFFS ) ;
if ( ! get_dbe ( tmp , ptr ) ) {
if ( tmp = = 0xdeadbeef )
return 1 ;
}
return 0 ;
}
2014-06-04 14:00:37 +04:00
static void ip22_check_gio ( int slotno , unsigned long addr , int irq )
2011-11-22 18:38:02 +04:00
{
const char * name = " Unknown " ;
struct gio_device * gio_dev ;
u32 tmp ;
__u8 id ;
int i ;
/* first look for GR2/GR3 by checking mystery register */
if ( ip22_is_gr2 ( addr ) )
tmp = 0x7f ;
else {
if ( ! ip22_gio_id ( addr , & tmp ) ) {
/*
2014-06-04 14:00:37 +04:00
* no GIO signature at start address of slot
* since Newport doesn ' t have one , we check if
* user status register is readable
2011-11-22 18:38:02 +04:00
*/
if ( ip22_gio_id ( addr + NEWPORT_USTATUS_OFFS , & tmp ) )
tmp = 0x7e ;
else
tmp = 0 ;
}
}
if ( tmp ) {
id = GIO_ID ( tmp ) ;
if ( tmp & GIO_32BIT_ID ) {
if ( tmp & GIO_64BIT_IFACE )
ip22_gio_set_64bit ( slotno ) ;
}
for ( i = 0 ; i < ARRAY_SIZE ( gio_name_table ) ; i + + ) {
if ( id = = gio_name_table [ i ] . id ) {
name = gio_name_table [ i ] . name ;
break ;
}
}
printk ( KERN_INFO " GIO: slot %d : %s (id %x) \n " ,
slotno , name , id ) ;
gio_dev = kzalloc ( sizeof * gio_dev , GFP_KERNEL ) ;
gio_dev - > name = name ;
gio_dev - > slotno = slotno ;
gio_dev - > id . id = id ;
gio_dev - > resource . start = addr ;
gio_dev - > resource . end = addr + 0x3fffff ;
gio_dev - > resource . flags = IORESOURCE_MEM ;
2014-06-04 14:00:37 +04:00
gio_dev - > irq = irq ;
2011-11-22 18:38:02 +04:00
dev_set_name ( & gio_dev - > dev , " %d " , slotno ) ;
gio_device_register ( gio_dev ) ;
} else
printk ( KERN_INFO " GIO: slot %d : Empty \n " , slotno ) ;
}
static struct bus_type gio_bus_type = {
2013-01-22 15:59:30 +04:00
. name = " gio " ,
2017-06-06 15:16:30 +03:00
. dev_groups = gio_dev_groups ,
2013-01-22 15:59:30 +04:00
. match = gio_bus_match ,
. probe = gio_device_probe ,
. remove = gio_device_remove ,
2011-11-22 18:38:02 +04:00
. shutdown = gio_device_shutdown ,
2013-01-22 15:59:30 +04:00
. uevent = gio_device_uevent ,
2011-11-22 18:38:02 +04:00
} ;
static struct resource gio_bus_resource = {
. start = GIO_SLOT_GFX_BASE ,
. end = GIO_SLOT_GFX_BASE + 0x9fffff ,
. name = " GIO Bus " ,
. flags = IORESOURCE_MEM ,
} ;
int __init ip22_gio_init ( void )
{
unsigned int pbdma __maybe_unused ;
int ret ;
ret = device_register ( & gio_bus ) ;
2013-12-19 19:03:27 +04:00
if ( ret ) {
put_device ( & gio_bus ) ;
2011-11-22 18:38:02 +04:00
return ret ;
2013-12-19 19:03:27 +04:00
}
2011-11-22 18:38:02 +04:00
ret = bus_register ( & gio_bus_type ) ;
if ( ! ret ) {
request_resource ( & iomem_resource , & gio_bus_resource ) ;
printk ( KERN_INFO " GIO: Probing bus... \n " ) ;
2014-06-04 14:00:37 +04:00
if ( ip22_is_fullhouse ( ) ) {
/* Indigo2 */
ip22_check_gio ( 0 , GIO_SLOT_GFX_BASE , SGI_GIO_1_IRQ ) ;
ip22_check_gio ( 1 , GIO_SLOT_EXP0_BASE , SGI_GIO_1_IRQ ) ;
2011-11-22 18:38:02 +04:00
} else {
2014-06-04 14:00:37 +04:00
/* Indy/Challenge S */
if ( get_dbe ( pbdma , ( unsigned int * ) & hpc3c1 - > pbdma [ 1 ] ) )
ip22_check_gio ( 0 , GIO_SLOT_GFX_BASE ,
SGI_GIO_0_IRQ ) ;
ip22_check_gio ( 1 , GIO_SLOT_EXP0_BASE , SGI_GIOEXP0_IRQ ) ;
ip22_check_gio ( 2 , GIO_SLOT_EXP1_BASE , SGI_GIOEXP1_IRQ ) ;
2011-11-22 18:38:02 +04:00
}
} else
device_unregister ( & gio_bus ) ;
return ret ;
}
subsys_initcall ( ip22_gio_init ) ;