blob: 3597e4cabec5b0ef9293bd22a052092586164b2c [file] [log] [blame] [edit]
/*
* arch/arm/mach-omap2/hsi.c
*
* HSI device definition
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Original Author: Sebastien JAN <[email protected]>
*
* This package 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.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/jiffies.h>
#include <linux/notifier.h>
#include <linux/hsi_driver_if.h>
#include <plat/omap_hsi.h>
#include <plat/omap_hwmod.h>
#include <plat/omap_device.h>
#include <../drivers/omap_hsi/hsi_driver.h>
#include "clock.h"
#include "mux.h"
#include "control.h"
#include "pm.h"
#include "dvfs.h"
static int omap_hsi_wakeup_enable(int hsi_port);
static int omap_hsi_wakeup_disable(int hsi_port);
#define OMAP_HSI_PLATFORM_DEVICE_DRIVER_NAME "omap_hsi"
#define OMAP_HSI_PLATFORM_DEVICE_NAME "omap_hsi.0"
#define OMAP_HSI_HWMOD_NAME "hsi"
#define OMAP_HSI_HWMOD_CLASSNAME "hsi"
#define OMAP_MUX_MODE_MASK 0x7
/* Hack till correct hwmod-mux api gets used */
#define CA_WAKE_MUX_REG (0x4a1000C2)
#define OMAP44XX_PADCONF_WAKEUPENABLE0 (1 << 14)
#define OMAP44XX_PADCONF_WAKEUPEVENT0 (1 << 15)
static int omap_mux_read_signal(const char *muxname)
{
u16 val = 0;
val = omap_readw(CA_WAKE_MUX_REG);
return val;
}
static int omap_mux_enable_wakeup(const char *muxname)
{
u16 val = 0;
val = omap_readw(CA_WAKE_MUX_REG);
val |= OMAP44XX_PADCONF_WAKEUPENABLE0;
omap_writew(val, CA_WAKE_MUX_REG);
return 0;
}
static int omap_mux_disable_wakeup(const char *muxname)
{
u16 val = 0;
val = omap_readw(CA_WAKE_MUX_REG);
val &= ~OMAP44XX_PADCONF_WAKEUPENABLE0;
omap_writew(val, CA_WAKE_MUX_REG);
return 0;
}
/*
* NOTE: We abuse a little bit the struct port_ctx to use it also for
* initialization.
*/
static struct hsi_port_ctx omap_hsi_port_ctx[] = {
[0] = {
.port_number = 1,
.hst.mode = HSI_MODE_FRAME,
.hst.flow = HSI_FLOW_SYNCHRONIZED,
.hst.frame_size = HSI_FRAMESIZE_DEFAULT,
.hst.divisor = HSI_DIVISOR_DEFAULT,
.hst.channels = HSI_CHANNELS_DEFAULT,
.hst.arb_mode = HSI_ARBMODE_ROUNDROBIN,
.hsr.mode = HSI_MODE_FRAME,
.hsr.flow = HSI_FLOW_SYNCHRONIZED,
.hsr.frame_size = HSI_FRAMESIZE_DEFAULT,
.hsr.channels = HSI_CHANNELS_DEFAULT,
.hsr.divisor = HSI_DIVISOR_DEFAULT,
.hsr.counters = HSI_COUNTERS_FT_DEFAULT |
HSI_COUNTERS_TB_DEFAULT |
HSI_COUNTERS_FB_DEFAULT,
.cawake_padconf_name = "usbb1_ulpitll_clk.hsi1_cawake",
.cawake_padconf_hsi_mode = OMAP_MUX_MODE1,
},
};
static struct hsi_ctrl_ctx omap_hsi_ctrl_ctx = {
.sysconfig = 0,
.gdd_gcr = 0,
.dll = 0,
.pctx = omap_hsi_port_ctx,
};
static struct hsi_platform_data omap_hsi_platform_data = {
.num_ports = ARRAY_SIZE(omap_hsi_port_ctx),
.hsi_gdd_chan_count = HSI_HSI_DMA_CHANNEL_MAX,
.default_hsi_fclk = HSI_DEFAULT_FCLK,
.fifo_mapping_strategy = HSI_FIFO_MAPPING_ALL_PORT1,
.ctx = &omap_hsi_ctrl_ctx,
.device_enable = omap_device_enable,
.device_idle = omap_device_idle,
.device_shutdown = omap_device_shutdown,
.device_scale = omap_device_scale,
.wakeup_enable = omap_hsi_wakeup_enable,
.wakeup_disable = omap_hsi_wakeup_disable,
.wakeup_is_from_hsi = omap_hsi_is_io_wakeup_from_hsi,
.board_suspend = omap_hsi_prepare_suspend,
};
static u32 omap_hsi_configure_errata(void)
{
u32 errata = 0;
if (cpu_is_omap44xx()) {
SET_HSI_ERRATA(errata, HSI_ERRATUM_i696_SW_RESET_FSM_STUCK);
SET_HSI_ERRATA(errata, HSI_ERRATUM_ixxx_3WIRES_NO_SWAKEUP);
SET_HSI_ERRATA(errata, HSI_ERRATUM_i702_PM_HSI_SWAKEUP);
}
return errata;
}
static struct platform_device *hsi_get_hsi_platform_device(void)
{
struct device *dev;
struct platform_device *pdev;
/* HSI_TODO: handle platform device id (or port) (0/1) */
dev = bus_find_device_by_name(&platform_bus_type, NULL,
OMAP_HSI_PLATFORM_DEVICE_NAME);
if (!dev) {
pr_debug("Could not find platform device %s\n",
OMAP_HSI_PLATFORM_DEVICE_NAME);
return 0;
}
if (!dev->driver) {
/* Could not find driver for platform device. */
return 0;
}
pdev = to_platform_device(dev);
return pdev;
}
static struct hsi_dev *hsi_get_hsi_controller_data(struct platform_device *pd)
{
struct hsi_dev *hsi_ctrl;
if (!pd)
return 0;
hsi_ctrl = (struct hsi_dev *) platform_get_drvdata(pd);
if (!hsi_ctrl) {
pr_err("Could not find HSI controller data\n");
return 0;
}
return hsi_ctrl;
}
/**
* hsi_get_hsi_port_ctx_data - Returns a pointer on the port context
*
* @hsi_port - port number to obtain context. Range [1, 2]
*
* Return value :* If success: pointer on the HSI port context requested
* * else NULL
*/
static struct hsi_port_ctx *hsi_get_hsi_port_ctx_data(int hsi_port)
{
int i;
for (i = 0; i < omap_hsi_platform_data.num_ports; i++)
if (omap_hsi_platform_data.ctx->pctx[i].port_number == hsi_port)
return &omap_hsi_platform_data.ctx->pctx[i];
return NULL;
}
/**
* omap_hsi_is_io_pad_hsi - Indicates if IO Pad has been muxed for HSI CAWAKE
*
* @hsi_port - port number to check for HSI muxing. Range [1, 2]
*
* Return value :* 0 if CAWAKE Padconf has not been found or CAWAKE not muxed for
* CAWAKE
* * else 1
*/
static int omap_hsi_is_io_pad_hsi(int hsi_port)
{
struct hsi_port_ctx *port_ctx;
u16 val;
port_ctx = hsi_get_hsi_port_ctx_data(hsi_port);
if (!port_ctx)
return 0;
/* Check for IO pad */
val = omap_mux_read_signal(port_ctx->cawake_padconf_name);
if (val == -ENODEV)
return 0;
/* Continue only if CAWAKE is muxed */
if ((val & OMAP_MUX_MODE_MASK) != port_ctx->cawake_padconf_hsi_mode)
return 0;
return 1;
}
/**
* omap_hsi_is_io_wakeup_from_hsi - Indicates an IO wakeup from HSI CAWAKE
*
* @hsi_port - returns port number which triggered wakeup. Range [1, 2].
* Only valid if return value is 1 (HSI wakeup detected)
*
* Return value :* false if CAWAKE Padconf has not been found or no IOWAKEUP event
* occured for CAWAKE.
* * true if HSI wakeup detected on port *hsi_port
*/
bool omap_hsi_is_io_wakeup_from_hsi(int *hsi_port)
{
struct hsi_port_ctx *port_ctx;
u16 val;
int i;
for (i = 0; i < omap_hsi_platform_data.num_ports; i++) {
port_ctx = &omap_hsi_platform_data.ctx->pctx[i];
/* Check for IO pad wakeup */
val = omap_mux_read_signal(port_ctx->cawake_padconf_name);
if (val == -ENODEV)
continue;
/* Continue only if CAWAKE is muxed */
if ((val & OMAP_MUX_MODE_MASK) !=
port_ctx->cawake_padconf_hsi_mode)
continue;
if (val & OMAP44XX_PADCONF_WAKEUPEVENT0) {
*hsi_port = port_ctx->port_number;
return true;
}
}
*hsi_port = 0;
return false;
}
/**
* omap_hsi_wakeup_enable - Enable HSI wakeup feature from RET/OFF mode
*
* @hsi_port - reference to the HSI port onto which enable wakeup feature.
* Range [1, 2]
*
* Return value :* 0 if CAWAKE has been configured to wakeup platform
* * -ENODEV if CAWAKE is not muxed on padconf
*/
static int omap_hsi_wakeup_enable(int hsi_port)
{
struct hsi_port_ctx *port_ctx;
int ret = -ENODEV;
if (omap_hsi_is_io_pad_hsi(hsi_port)) {
port_ctx = hsi_get_hsi_port_ctx_data(hsi_port);
ret = omap_mux_enable_wakeup(port_ctx->cawake_padconf_name);
omap4_trigger_ioctrl();
} else {
pr_debug("HSI port %d not muxed, failed to enable IO wakeup\n",
hsi_port);
}
return ret;
}
/**
* omap_hsi_wakeup_disable - Disable HSI wakeup feature from RET/OFF mode
*
* @hsi_port - reference to the HSI port onto which disable wakeup feature.
* Range [1, 2]
*
* Return value :* 0 if CAWAKE has been configured to not wakeup platform
* * -ENODEV if CAWAKE is not muxed on padconf
*/
static int omap_hsi_wakeup_disable(int hsi_port)
{
struct hsi_port_ctx *port_ctx;
int ret = -ENODEV;
if (omap_hsi_is_io_pad_hsi(hsi_port)) {
port_ctx = hsi_get_hsi_port_ctx_data(hsi_port);
ret = omap_mux_disable_wakeup(port_ctx->cawake_padconf_name);
omap4_trigger_ioctrl();
} else {
pr_debug("HSI port %d not muxed, failed to disable IO wakeup\n",
hsi_port);
}
return ret;
}
/**
* omap_hsi_prepare_suspend - Prepare HSI for suspend mode
*
* @hsi_port - reference to the HSI port. Range [1, 2]
* @dev_may_wakeup - value of sysfs flag indicating device wakeup capability
*
* Return value :* 0 if CAWAKE padconf has been configured properly
* * -ENODEV if CAWAKE is not muxed on padconf.
*
*/
int omap_hsi_prepare_suspend(int hsi_port, bool dev_may_wakeup)
{
int ret;
if (dev_may_wakeup)
ret = omap_hsi_wakeup_enable(hsi_port);
else
ret = omap_hsi_wakeup_disable(hsi_port);
return ret;
}
/**
* omap_hsi_io_wakeup_check - Check if IO wakeup is from HSI and schedule HSI
* processing tasklet
*
* Return value : * 0 if HSI tasklet scheduled.
* * negative value else.
*/
int omap_hsi_io_wakeup_check(void)
{
int hsi_port, ret = -1;
/* Modem HSI wakeup */
if (omap_hsi_is_io_wakeup_from_hsi(&hsi_port))
ret = omap_hsi_wakeup(hsi_port);
return ret;
}
/**
* omap_hsi_wakeup - Prepare HSI for wakeup from suspend mode (RET/OFF)
*
* @hsi_port - reference to the HSI port which triggered wakeup.
* Range [1, 2]
*
* Return value : * 0 if HSI tasklet scheduled.
* * negative value else.
*/
int omap_hsi_wakeup(int hsi_port)
{
static struct platform_device *pdev;
static struct hsi_dev *hsi_ctrl;
int i;
if (!pdev) {
pdev = hsi_get_hsi_platform_device();
if (!pdev)
return -ENODEV;
}
if (!device_may_wakeup(&pdev->dev)) {
dev_info(&pdev->dev, "Modem not allowed to wakeup platform\n");
return -EPERM;
}
if (!hsi_ctrl) {
hsi_ctrl = hsi_get_hsi_controller_data(pdev);
if (!hsi_ctrl)
return -ENODEV;
}
for (i = 0; i < omap_hsi_platform_data.num_ports; i++) {
if (omap_hsi_platform_data.ctx->pctx[i].port_number == hsi_port)
break;
}
if (i == omap_hsi_platform_data.num_ports)
return -ENODEV;
/* Check no other interrupt handler has already scheduled the tasklet */
if (test_and_set_bit(HSI_FLAGS_TASKLET_LOCK,
&hsi_ctrl->hsi_port[i].flags))
return -EBUSY;
dev_dbg(hsi_ctrl->dev, "Modem wakeup detected from HSI CAWAKE Pad port "
"%d\n", hsi_port);
/* CAWAKE falling or rising edge detected */
hsi_ctrl->hsi_port[i].cawake_off_event = true;
tasklet_hi_schedule(&hsi_ctrl->hsi_port[i].hsi_tasklet);
/* Disable interrupt until Bottom Half has cleared */
/* the IRQ status register */
disable_irq_nosync(hsi_ctrl->hsi_port[i].irq);
return 0;
}
/* HSI_TODO : This requires some fine tuning & completion of
* activate/deactivate latency values
*/
static struct omap_device_pm_latency omap_hsi_latency[] = {
[0] = {
.deactivate_func = omap_device_idle_hwmods,
.activate_func = omap_device_enable_hwmods,
.flags = OMAP_DEVICE_LATENCY_AUTO_ADJUST,
},
};
/* HSI device registration */
static int __init omap_hsi_register(struct omap_hwmod *oh, void *user)
{
struct omap_device *od;
struct hsi_platform_data *pdata = &omap_hsi_platform_data;
if (!oh) {
pr_err("Could not look up %s omap_hwmod\n",
OMAP_HSI_HWMOD_NAME);
return -EEXIST;
}
omap_hsi_platform_data.errata = omap_hsi_configure_errata();
od = omap_device_build(OMAP_HSI_PLATFORM_DEVICE_DRIVER_NAME, 0, oh,
pdata, sizeof(*pdata), omap_hsi_latency,
ARRAY_SIZE(omap_hsi_latency), false);
WARN(IS_ERR(od), "Can't build omap_device for %s:%s.\n",
OMAP_HSI_PLATFORM_DEVICE_DRIVER_NAME, oh->name);
pr_info("HSI: device registered as omap_hwmod: %s\n", oh->name);
return 0;
}
static void __init omap_4430hsi_pad_conf(void)
{
/*
* HSI pad conf: hsi1_ca/ac_wake/flag/data/ready
* Also configure gpio_92/95/157/187 used by modem
*/
/* hsi1_cawake */
omap_mux_init_signal("usbb1_ulpitll_clk.hsi1_cawake", \
OMAP_PIN_INPUT_PULLDOWN | \
OMAP_PIN_OFF_NONE | \
OMAP_PIN_OFF_WAKEUPENABLE);
/* hsi1_caflag */
omap_mux_init_signal("usbb1_ulpitll_dir.hsi1_caflag", \
OMAP_PIN_INPUT | \
OMAP_PIN_OFF_NONE);
/* hsi1_cadata */
omap_mux_init_signal("usbb1_ulpitll_stp.hsi1_cadata", \
OMAP_PIN_INPUT | \
OMAP_PIN_OFF_NONE);
/* hsi1_acready */
omap_mux_init_signal("usbb1_ulpitll_nxt.hsi1_acready", \
OMAP_PIN_OUTPUT | \
OMAP_OFF_EN);
/* hsi1_acwake */
omap_mux_init_signal("usbb1_ulpitll_dat0.hsi1_acwake", \
OMAP_PIN_OUTPUT | \
OMAP_OFF_EN);
/* hsi1_acdata */
omap_mux_init_signal("usbb1_ulpitll_dat1.hsi1_acdata", \
OMAP_PIN_OUTPUT | \
OMAP_OFF_EN);
/* hsi1_acflag */
omap_mux_init_signal("usbb1_ulpitll_dat2.hsi1_acflag", \
OMAP_PIN_OUTPUT | \
OMAP_OFF_EN);
/* hsi1_caready */
omap_mux_init_signal("usbb1_ulpitll_dat3.hsi1_caready", \
OMAP_PIN_INPUT | \
OMAP_PIN_OFF_NONE);
/* gpio_92 */
omap_mux_init_signal("usbb1_ulpitll_dat4.gpio_92", \
OMAP_PULL_ENA);
/* gpio_95 */
omap_mux_init_signal("usbb1_ulpitll_dat7.gpio_95", \
OMAP_PIN_INPUT_PULLDOWN | \
OMAP_PIN_OFF_NONE);
/* gpio_157 */
omap_mux_init_signal("usbb2_ulpitll_clk.gpio_157", \
OMAP_PIN_OUTPUT | \
OMAP_PIN_OFF_NONE);
/* gpio_187 */
omap_mux_init_signal("sys_boot3.gpio_187", \
OMAP_PIN_OUTPUT | \
OMAP_PIN_OFF_NONE);
}
int __init omap_hsi_dev_init(void)
{
/* Keep this for genericity, although there is only one hwmod for HSI */
return omap_hwmod_for_each_by_class(OMAP_HSI_HWMOD_CLASSNAME,
omap_hsi_register, NULL);
}
postcore_initcall(omap_hsi_dev_init);
/* HSI devices registration */
int __init omap_hsi_init(void)
{
omap_4430hsi_pad_conf();
return 0;
}