2015-10-13 13:29:02 +03:00
/*
* 8250 _mid . c - Driver for UART on Intel Penwell and various other Intel SOCs
*
* Copyright ( C ) 2015 Intel Corporation
* Author : Heikki Krogerus < heikki . krogerus @ linux . intel . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/rational.h>
# include <linux/module.h>
# include <linux/pci.h>
# include <linux/dma/hsu.h>
# include "8250.h"
# define PCI_DEVICE_ID_INTEL_PNW_UART1 0x081b
# define PCI_DEVICE_ID_INTEL_PNW_UART2 0x081c
# define PCI_DEVICE_ID_INTEL_PNW_UART3 0x081d
# define PCI_DEVICE_ID_INTEL_TNG_UART 0x1191
2015-10-13 13:29:06 +03:00
# define PCI_DEVICE_ID_INTEL_DNV_UART 0x19d8
2015-10-13 13:29:02 +03:00
/* Intel MID Specific registers */
# define INTEL_MID_UART_PS 0x30
# define INTEL_MID_UART_MUL 0x34
# define INTEL_MID_UART_DIV 0x38
struct mid8250 ;
struct mid8250_board {
unsigned long freq ;
unsigned int base_baud ;
int ( * setup ) ( struct mid8250 * , struct uart_port * p ) ;
2015-10-13 13:29:06 +03:00
void ( * exit ) ( struct mid8250 * ) ;
2015-10-13 13:29:02 +03:00
} ;
struct mid8250 {
int line ;
int dma_index ;
struct pci_dev * dma_dev ;
struct uart_8250_dma dma ;
struct mid8250_board * board ;
2015-10-13 13:29:06 +03:00
struct hsu_dma_chip dma_chip ;
2015-10-13 13:29:02 +03:00
} ;
/*****************************************************************************/
static int pnw_setup ( struct mid8250 * mid , struct uart_port * p )
{
struct pci_dev * pdev = to_pci_dev ( p - > dev ) ;
switch ( pdev - > device ) {
case PCI_DEVICE_ID_INTEL_PNW_UART1 :
mid - > dma_index = 0 ;
break ;
case PCI_DEVICE_ID_INTEL_PNW_UART2 :
mid - > dma_index = 1 ;
break ;
case PCI_DEVICE_ID_INTEL_PNW_UART3 :
mid - > dma_index = 2 ;
break ;
default :
return - EINVAL ;
}
mid - > dma_dev = pci_get_slot ( pdev - > bus ,
PCI_DEVFN ( PCI_SLOT ( pdev - > devfn ) , 3 ) ) ;
return 0 ;
}
static int tng_setup ( struct mid8250 * mid , struct uart_port * p )
{
struct pci_dev * pdev = to_pci_dev ( p - > dev ) ;
int index = PCI_FUNC ( pdev - > devfn ) ;
/* Currently no support for HSU port0 */
if ( index - - = = 0 )
return - ENODEV ;
mid - > dma_index = index ;
mid - > dma_dev = pci_get_slot ( pdev - > bus , PCI_DEVFN ( 5 , 0 ) ) ;
return 0 ;
}
2015-10-13 13:29:06 +03:00
static int dnv_handle_irq ( struct uart_port * p )
{
struct mid8250 * mid = p - > private_data ;
int ret ;
ret = hsu_dma_irq ( & mid - > dma_chip , 0 ) ;
ret | = hsu_dma_irq ( & mid - > dma_chip , 1 ) ;
/* For now, letting the HW generate separate interrupt for the UART */
if ( ret )
return ret ;
return serial8250_handle_irq ( p , serial_port_in ( p , UART_IIR ) ) ;
}
# define DNV_DMA_CHAN_OFFSET 0x80
static int dnv_setup ( struct mid8250 * mid , struct uart_port * p )
{
struct hsu_dma_chip * chip = & mid - > dma_chip ;
struct pci_dev * pdev = to_pci_dev ( p - > dev ) ;
int ret ;
chip - > dev = & pdev - > dev ;
chip - > irq = pdev - > irq ;
chip - > regs = p - > membase ;
chip - > length = pci_resource_len ( pdev , 0 ) ;
chip - > offset = DNV_DMA_CHAN_OFFSET ;
/* Falling back to PIO mode if DMA probing fails */
ret = hsu_dma_probe ( chip ) ;
if ( ret )
return 0 ;
mid - > dma_dev = pdev ;
p - > handle_irq = dnv_handle_irq ;
return 0 ;
}
static void dnv_exit ( struct mid8250 * mid )
{
if ( ! mid - > dma_dev )
return ;
hsu_dma_remove ( & mid - > dma_chip ) ;
}
2015-10-13 13:29:02 +03:00
/*****************************************************************************/
static void mid8250_set_termios ( struct uart_port * p ,
struct ktermios * termios ,
struct ktermios * old )
{
unsigned int baud = tty_termios_baud_rate ( termios ) ;
struct mid8250 * mid = p - > private_data ;
unsigned short ps = 16 ;
unsigned long fuart = baud * ps ;
unsigned long w = BIT ( 24 ) - 1 ;
unsigned long mul , div ;
if ( mid - > board - > freq < fuart ) {
/* Find prescaler value that satisfies Fuart < Fref */
if ( mid - > board - > freq > baud )
ps = mid - > board - > freq / baud ; /* baud rate too high */
else
ps = 1 ; /* PLL case */
fuart = baud * ps ;
} else {
/* Get Fuart closer to Fref */
fuart * = rounddown_pow_of_two ( mid - > board - > freq / fuart ) ;
}
rational_best_approximation ( fuart , mid - > board - > freq , w , w , & mul , & div ) ;
p - > uartclk = fuart * 16 / ps ; /* core uses ps = 16 always */
writel ( ps , p - > membase + INTEL_MID_UART_PS ) ; /* set PS */
writel ( mul , p - > membase + INTEL_MID_UART_MUL ) ; /* set MUL */
writel ( div , p - > membase + INTEL_MID_UART_DIV ) ;
serial8250_do_set_termios ( p , termios , old ) ;
}
static bool mid8250_dma_filter ( struct dma_chan * chan , void * param )
{
struct hsu_dma_slave * s = param ;
if ( s - > dma_dev ! = chan - > device - > dev | | s - > chan_id ! = chan - > chan_id )
return false ;
chan - > private = s ;
return true ;
}
static int mid8250_dma_setup ( struct mid8250 * mid , struct uart_8250_port * port )
{
struct uart_8250_dma * dma = & mid - > dma ;
struct device * dev = port - > port . dev ;
struct hsu_dma_slave * rx_param ;
struct hsu_dma_slave * tx_param ;
2015-10-13 13:29:06 +03:00
if ( ! mid - > dma_dev )
return 0 ;
2015-10-13 13:29:02 +03:00
rx_param = devm_kzalloc ( dev , sizeof ( * rx_param ) , GFP_KERNEL ) ;
if ( ! rx_param )
return - ENOMEM ;
tx_param = devm_kzalloc ( dev , sizeof ( * tx_param ) , GFP_KERNEL ) ;
if ( ! tx_param )
return - ENOMEM ;
rx_param - > chan_id = mid - > dma_index * 2 + 1 ;
tx_param - > chan_id = mid - > dma_index * 2 ;
dma - > rxconf . src_maxburst = 64 ;
dma - > txconf . dst_maxburst = 64 ;
rx_param - > dma_dev = & mid - > dma_dev - > dev ;
tx_param - > dma_dev = & mid - > dma_dev - > dev ;
dma - > fn = mid8250_dma_filter ;
dma - > rx_param = rx_param ;
dma - > tx_param = tx_param ;
port - > dma = dma ;
return 0 ;
}
static int mid8250_probe ( struct pci_dev * pdev , const struct pci_device_id * id )
{
struct uart_8250_port uart ;
struct mid8250 * mid ;
int ret ;
ret = pcim_enable_device ( pdev ) ;
if ( ret )
return ret ;
pci_set_master ( pdev ) ;
mid = devm_kzalloc ( & pdev - > dev , sizeof ( * mid ) , GFP_KERNEL ) ;
if ( ! mid )
return - ENOMEM ;
mid - > board = ( struct mid8250_board * ) id - > driver_data ;
memset ( & uart , 0 , sizeof ( struct uart_8250_port ) ) ;
uart . port . dev = & pdev - > dev ;
uart . port . irq = pdev - > irq ;
uart . port . private_data = mid ;
uart . port . type = PORT_16750 ;
uart . port . iotype = UPIO_MEM ;
uart . port . uartclk = mid - > board - > base_baud * 16 ;
uart . port . flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE ;
uart . port . set_termios = mid8250_set_termios ;
uart . port . mapbase = pci_resource_start ( pdev , 0 ) ;
uart . port . membase = pcim_iomap ( pdev , 0 , 0 ) ;
if ( ! uart . port . membase )
return - ENOMEM ;
if ( mid - > board - > setup ) {
ret = mid - > board - > setup ( mid , & uart . port ) ;
if ( ret )
return ret ;
}
ret = mid8250_dma_setup ( mid , & uart ) ;
if ( ret )
2015-10-13 13:29:06 +03:00
goto err ;
2015-10-13 13:29:02 +03:00
ret = serial8250_register_8250_port ( & uart ) ;
if ( ret < 0 )
2015-10-13 13:29:06 +03:00
goto err ;
2015-10-13 13:29:02 +03:00
mid - > line = ret ;
pci_set_drvdata ( pdev , mid ) ;
return 0 ;
2015-10-13 13:29:06 +03:00
err :
if ( mid - > board - > exit )
mid - > board - > exit ( mid ) ;
return ret ;
2015-10-13 13:29:02 +03:00
}
static void mid8250_remove ( struct pci_dev * pdev )
{
struct mid8250 * mid = pci_get_drvdata ( pdev ) ;
2015-10-13 13:29:06 +03:00
if ( mid - > board - > exit )
mid - > board - > exit ( mid ) ;
2015-10-13 13:29:02 +03:00
serial8250_unregister_port ( mid - > line ) ;
}
static const struct mid8250_board pnw_board = {
. freq = 50000000 ,
. base_baud = 115200 ,
. setup = pnw_setup ,
} ;
static const struct mid8250_board tng_board = {
. freq = 38400000 ,
. base_baud = 1843200 ,
. setup = tng_setup ,
} ;
2015-10-13 13:29:06 +03:00
static const struct mid8250_board dnv_board = {
. freq = 133333333 ,
. base_baud = 115200 ,
. setup = dnv_setup ,
. exit = dnv_exit ,
} ;
2015-10-13 13:29:02 +03:00
# define MID_DEVICE(id, board) { PCI_VDEVICE(INTEL, id), (kernel_ulong_t)&board }
static const struct pci_device_id pci_ids [ ] = {
MID_DEVICE ( PCI_DEVICE_ID_INTEL_PNW_UART1 , pnw_board ) ,
MID_DEVICE ( PCI_DEVICE_ID_INTEL_PNW_UART2 , pnw_board ) ,
MID_DEVICE ( PCI_DEVICE_ID_INTEL_PNW_UART3 , pnw_board ) ,
MID_DEVICE ( PCI_DEVICE_ID_INTEL_TNG_UART , tng_board ) ,
2015-10-13 13:29:06 +03:00
MID_DEVICE ( PCI_DEVICE_ID_INTEL_DNV_UART , dnv_board ) ,
2015-10-13 13:29:02 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( pci , pci_ids ) ;
static struct pci_driver mid8250_pci_driver = {
. name = " 8250_mid " ,
. id_table = pci_ids ,
. probe = mid8250_probe ,
. remove = mid8250_remove ,
} ;
module_pci_driver ( mid8250_pci_driver ) ;
MODULE_AUTHOR ( " Intel Corporation " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " Intel MID UART driver " ) ;