blob: 8045d59bc1a5c4be705a1a632c537dc70a04e37d [file] [log] [blame]
/* lkm.c
*
* Linux Kernel Module implementation for Platform device drivers
*
*/
/*****************************************************************************
* Copyright (c) 2016-2020 by Rambus, Inc. and/or its subsidiaries.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
/*----------------------------------------------------------------------------
* This module implements (provides) the following interface(s):
*/
// Linux Kernel Module interface
#include "lkm.h"
/*----------------------------------------------------------------------------
* This module uses (requires) the following interface(s):
*/
// Default configuration
#include "c_lkm.h"
// Driver Framework Basic Definitions API
#include "basic_defs.h" // uint32_t, NULL, inline, bool,
// IDENTIFIER_NOT_USED
// Driver Framework C Run-Time Library API
#include "clib.h" // memcmp
// Logging API
#undef LOG_SEVERITY_MAX
#define LOG_SEVERITY_MAX LKM_LOG_SEVERITY
#include "log.h" // LOG_*
// Linux Kernel API
#include <linux/types.h> // phys_addr_t, resource_size_t
#include <linux/version.h> // LINUX_VERSION_CODE, KERNEL_VERSION
#include <linux/device.h> // struct device
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h> // platform_*,
#include <linux/of_platform.h> // of_*,
#include <asm/io.h> // ioremap, iounmap
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/moduleparam.h>
#include <linux/clk.h> // clk_*
/*----------------------------------------------------------------------------
* Definitions and macros
*/
// LKM PCI implementation administration data
typedef struct
{
// declarations native to Linux kernel
struct platform_device * Platform_Device_p;
// virtual address returned by ioremap()
uint32_t __iomem * MappedBaseAddr_p;
// physical address passed to ioremap()
phys_addr_t PhysBaseAddr;
// Platform device driver data
struct platform_driver Platform_Driver;
// Device resource (IO space) identifier
int ResId;
// Device resource size in bytes
resource_size_t ResByteCount;
// Device virtual IRQ number
int VirtIrqNr [LKM_PLATFORM_IRQ_COUNT];
bool fRetainMap;
// Initialization flag, true - initialized, otherwise - false
bool fInitialized;
} LKM_Admin_t;
/*----------------------------------------------------------------------------
* Local variables
*/
static struct clk *gateclk;
static const struct of_device_id LKM_DT_IDTable[] =
{
{ .compatible = LKM_PLATFORM_DEVICE_NAME, },
{ },
};
// LKM administration data
static LKM_Admin_t LKM_Admin;
/*----------------------------------------------------------------------------
* LKM_Probe
*/
static int
LKM_Probe(
struct platform_device * Platform_Device_p)
{
int i;
LKM_Admin_t * p = &LKM_Admin;
struct resource * rsc_p = NULL;
LOG_INFO(LKM_LOG_PREFIX "%s: entered\n", __func__);
if (Platform_Device_p == NULL)
{
LOG_CRIT(LKM_LOG_PREFIX
"%s: failed, missing platform device\n",
__func__);
return -ENODEV;
}
if (of_find_compatible_node(NULL, NULL, LKM_PLATFORM_DEVICE_NAME))
{
LOG_INFO(LKM_LOG_PREFIX
"%s: found requested device %s\n",
__func__,
LKM_PLATFORM_DEVICE_NAME);
}
else
{
LOG_CRIT(LKM_LOG_PREFIX
"%s: device %s not supported\n",
__func__,
LKM_PLATFORM_DEVICE_NAME);
return -ENODEV;
}
// remember the device reference
p->Platform_Device_p = Platform_Device_p;
if (p->fRetainMap)
{
// get platform device physical address
// Exported under GPL
rsc_p = platform_get_resource(Platform_Device_p,
IORESOURCE_MEM,
p->ResId);
// device tree specific for this OF device
// only 32-bit physical addresses are supported
if(rsc_p)
{
LOG_INFO(LKM_LOG_PREFIX
"%s: mem start=%08x, end=%08x, flags=%08x\n",
__func__,
(unsigned int)rsc_p->start,
(unsigned int)rsc_p->end,
(unsigned int)rsc_p->flags);
}
else
{
LOG_CRIT(LKM_LOG_PREFIX
"%s: memory resource id %d not found\n",
__func__,
p->ResId);
return -ENODEV;
}
p->PhysBaseAddr = rsc_p->start;
p->ResByteCount = rsc_p->end - rsc_p->start + 1;
// now map the chip into kernel memory
// so we can access the EIP static resources
// note: ioremap is uncached by default
p->MappedBaseAddr_p = ioremap(p->PhysBaseAddr, p->ResByteCount);
if (p->MappedBaseAddr_p == NULL)
{
LOG_CRIT(LKM_LOG_PREFIX
"%s: failed to ioremap platform driver %s, "
"resource id %d, phys addr 0x%p, size %ul\n",
__func__,
p->Platform_Driver.driver.name,
p->ResId,
(void*)p->PhysBaseAddr,
(unsigned int)p->ResByteCount);
return -ENODEV;
}
LOG_INFO(LKM_LOG_PREFIX
"%s: Mapped platform driver %s addr %p, phys addr 0x%p, "
"sizeof(resource_size_t)=%d\n resource id=%d, size=%ul\n",
__func__,
p->Platform_Driver.driver.name,
p->MappedBaseAddr_p,
(void*)p->PhysBaseAddr,
(int)sizeof(resource_size_t),
p->ResId,
(unsigned int)p->ResByteCount);
}
// Optional device clock control functionality,
// only required when the device clock needs to be enabled via the kernel
gateclk = clk_get(&Platform_Device_p->dev, NULL);
if (!IS_ERR(gateclk))
{
// Exported under GPL
clk_prepare_enable(gateclk);
LOG_INFO("%s: clk_prepare_enable() successful\n", __func__);
}
else
{
// Not all devices support it
LOG_INFO("%s: clk_get() could not obtain clock\n", __func__);
}
for (i = 0; i < LKM_PLATFORM_IRQ_COUNT; i++)
{
int IrqNr, IrqIndex;
IrqIndex = LKM_PLATFORM_IRQ_COUNT > 1 ? i : LKM_PLATFORM_IRQ_IDX;
// Exported under GPL
IrqNr = platform_get_irq(Platform_Device_p, IrqIndex);
if (IrqNr < 0)
{
LOG_INFO(LKM_LOG_PREFIX "%s: failed to get IRQ for index %d\n",
__func__,
IrqIndex);
return -ENODEV;
}
else
p->VirtIrqNr[i] = IrqNr;
}
LOG_INFO(LKM_LOG_PREFIX "%s: left\n", __func__);
// return 0 to indicate "we decided to take ownership"
return 0;
}
/*----------------------------------------------------------------------------
* LKM_Remove
*/
static int
LKM_Remove(
struct platform_device * Platform_Device_p)
{
LKM_Admin_t * p = &LKM_Admin;
LOG_INFO(LKM_LOG_PREFIX "%s: entered\n", __func__);
if (p->Platform_Device_p != Platform_Device_p)
{
LOG_CRIT(LKM_LOG_PREFIX
"%s: failed, missing or wrong platform device\n",
__func__);
return -ENODEV;
}
LOG_INFO(LKM_LOG_PREFIX
"%s: mapped base addr=%p\n", __func__, p->MappedBaseAddr_p);
// Optional device clock control functionality
if (!IS_ERR(gateclk))
{
// Exported under GPL
clk_disable_unprepare(gateclk);
clk_put(gateclk);
}
if (p->MappedBaseAddr_p && p->fRetainMap)
{
iounmap(p->MappedBaseAddr_p);
p->MappedBaseAddr_p = NULL;
}
LOG_INFO(LKM_LOG_PREFIX "%s: left\n", __func__);
return 0;
}
/*-----------------------------------------------------------------------------
* LKM_Init
*/
int
LKM_Init(
LKM_Init_t * const InitData_p)
{
int Status;
LKM_Admin_t * p = &LKM_Admin;
LOG_INFO(LKM_LOG_PREFIX "%s: entered\n", __func__);
// Check input parameters
if(InitData_p == NULL)
{
LOG_CRIT(LKM_LOG_PREFIX "%s: failed, missing init data\n", __func__);
return -1;
}
if (p->fInitialized)
{
LOG_CRIT(LKM_LOG_PREFIX "%s: failed, already initialized\n", __func__);
return -2;
}
// Fill in PCI device driver data
p->fRetainMap = InitData_p->fRetainMap;
p->ResId = InitData_p->ResId; // not used
p->Platform_Driver.probe = LKM_Probe;
p->Platform_Driver.remove = LKM_Remove;
p->Platform_Driver.driver.name = InitData_p->DriverName_p;
p->Platform_Driver.driver.owner = THIS_MODULE;
p->Platform_Driver.driver.pm = InitData_p->PM_p;
p->Platform_Driver.driver.of_match_table = LKM_DT_IDTable;
// Exported under GPL
Status = platform_driver_register(&p->Platform_Driver);
if (Status < 0)
{
LOG_CRIT(LKM_LOG_PREFIX
"%s: failed to register platform device driver\n",
__func__);
return -3;
}
if (p->Platform_Device_p == NULL)
{
LOG_CRIT(LKM_LOG_PREFIX "%s: failed, no device detected\n", __func__);
platform_driver_unregister(&p->Platform_Driver);
return -4;
}
// if provided, CustomInitData_p points to an "int"
// we return a pointer to the arrays of irq numbers
InitData_p->CustomInitData_p = p->VirtIrqNr;
p->fInitialized = true;
LOG_INFO(LKM_LOG_PREFIX "%s: left\n", __func__);
return 0; // success
}
/*-----------------------------------------------------------------------------
* LKM_Uninit
*/
void
LKM_Uninit(void)
{
LKM_Admin_t * p = &LKM_Admin;
LOG_INFO(LKM_LOG_PREFIX "%s: entered\n", __func__);
if (!p->fInitialized)
{
LOG_CRIT(LKM_LOG_PREFIX "%s: failed, not initialized yet\n", __func__);
return;
}
LOG_INFO(LKM_LOG_PREFIX
"%s: calling platform_driver_unregister\n",
__func__);
platform_driver_unregister(&p->Platform_Driver);
ZEROINIT(LKM_Admin); //p->fInitialized = false;
LOG_INFO(LKM_LOG_PREFIX "%s: left\n", __func__);
}
/*-----------------------------------------------------------------------------
* LKM_DeviceGeneric_Get
*/
void *
LKM_DeviceGeneric_Get(void)
{
LKM_Admin_t * p = &LKM_Admin;
return &p->Platform_Device_p->dev;
}
/*-----------------------------------------------------------------------------
* LKM_DeviceSpecific_Get
*/
void *
LKM_DeviceSpecific_Get(void)
{
LKM_Admin_t * p = &LKM_Admin;
return p->Platform_Device_p;
}
/*-----------------------------------------------------------------------------
* LKM_PhysBaseAddr_Get
*/
void *
LKM_PhysBaseAddr_Get(void)
{
LKM_Admin_t * p = &LKM_Admin;
return (void*)p->PhysBaseAddr;
}
/*-----------------------------------------------------------------------------
* LKM_MappedBaseAddr_Get
*/
void __iomem *
LKM_MappedBaseAddr_Get(void)
{
LKM_Admin_t * p = &LKM_Admin;
return p->MappedBaseAddr_p;
}
/* end of file lkm.c */