blob: 87df90a4d737149ef882c617802041f4df8017a3 [file] [log] [blame]
/*
* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/mmc/sdio_func.h>
#include <linux/list.h>
#include <linux/slab.h>
#ifdef CONFIG_PLD_SDIO_CNSS
#include <net/cnss.h>
#endif
#include "pld_common.h"
#include "pld_internal.h"
#include "pld_sdio.h"
#ifdef CONFIG_SDIO
/* SDIO manufacturer ID and Codes */
#define MANUFACTURER_ID_AR6320_BASE 0x500
#define MANUFACTURER_ID_QCA9377_BASE 0x700
#define MANUFACTURER_CODE 0x271
/**
* pld_sdio_probe() - Probe function for SDIO platform driver
* sdio_func: pointer to sdio device function
* @id: SDIO device ID table
*
* The probe function will be called when SDIO device provided
* in the ID table is detected.
*
* Return: int
*/
static int pld_sdio_probe(struct sdio_func *sdio_func,
const struct sdio_device_id *id)
{
struct pld_context *pld_context;
struct device *dev = &sdio_func->dev;
int ret = 0;
pld_context = pld_get_global_context();
if (!pld_context) {
ret = -ENODEV;
goto out;
}
ret = pld_add_dev(pld_context, dev, PLD_BUS_TYPE_SDIO);
if (ret)
goto out;
return pld_context->ops->probe(dev, PLD_BUS_TYPE_SDIO,
sdio_func, (void *)id);
out:
return ret;
}
/**
* pld_sdio_remove() - Remove function for SDIO device
* @sdio_func: pointer to sdio device function
*
* The remove function will be called when SDIO device is disconnected
*
* Return: void
*/
static void pld_sdio_remove(struct sdio_func *sdio_func)
{
struct pld_context *pld_context;
struct device *dev = &sdio_func->dev;
pld_context = pld_get_global_context();
if (!pld_context)
return;
pld_context->ops->remove(dev, PLD_BUS_TYPE_SDIO);
pld_del_dev(pld_context, dev);
}
#ifdef CONFIG_PLD_SDIO_CNSS
/**
* pld_sdio_reinit() - SSR re-initialize function for SDIO device
* @sdio_func: pointer to sdio device function
* @id: SDIO device ID
*
* During subsystem restart(SSR), this function will be called to
* re-initialize SDIO device.
*
* Return: int
*/
static int pld_sdio_reinit(struct sdio_func *sdio_func,
const struct sdio_device_id *id)
{
struct pld_context *pld_context;
struct device *dev = &sdio_func->dev;
pld_context = pld_get_global_context();
if (pld_context->ops->reinit)
return pld_context->ops->reinit(dev, PLD_BUS_TYPE_SDIO,
sdio_func, (void *)id);
return -ENODEV;
}
/**
* pld_sdio_shutdown() - SSR shutdown function for SDIO device
* @sdio_func: pointer to sdio device function
*
* During SSR, this function will be called to shutdown SDIO device.
*
* Return: void
*/
static void pld_sdio_shutdown(struct sdio_func *sdio_func)
{
struct pld_context *pld_context;
struct device *dev = &sdio_func->dev;
pld_context = pld_get_global_context();
if (pld_context->ops->shutdown)
pld_context->ops->shutdown(dev, PLD_BUS_TYPE_SDIO);
}
/**
* pld_sdio_crash_shutdown() - Crash shutdown function for SDIO device
* @sdio_func: pointer to sdio device function
*
* This function will be called when a crash is detected, it will shutdown
* the SDIO device.
*
* Return: void
*/
static void pld_sdio_crash_shutdown(struct sdio_func *sdio_func)
{
struct pld_context *pld_context;
struct device *dev = &sdio_func->dev;
pld_context = pld_get_global_context();
if (pld_context->ops->crash_shutdown)
pld_context->ops->crash_shutdown(dev, PLD_BUS_TYPE_SDIO);
}
#endif
#ifdef CONFIG_PM
/**
* pld_sdio_suspend() - Suspend callback function for power management
* @dev: SDIO device
*
* This function is to suspend the SDIO device when power management is
* enabled.
*
* Return: void
*/
static int pld_sdio_suspend(struct device *dev)
{
struct pld_context *pld_context;
pm_message_t state = { .event = PM_EVENT_SUSPEND };
pld_context = pld_get_global_context();
return pld_context->ops->suspend(dev,
PLD_BUS_TYPE_SDIO, state);
}
/**
* pld_sdio_resume() - Resume callback function for power management
* @dev: SDIO device
*
* This function is to resume the SDIO device when power management is
* enabled.
*
* Return: void
*/
static int pld_sdio_resume(struct device *dev)
{
struct pld_context *pld_context;
pld_context = pld_get_global_context();
return pld_context->ops->resume(dev, PLD_BUS_TYPE_SDIO);
}
#endif
static struct sdio_device_id pld_sdio_id_table[] = {
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x0))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x1))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x2))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x3))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x4))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x5))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x6))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x7))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x8))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x9))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xA))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xB))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xC))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xD))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xE))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xF))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x0))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x1))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x2))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x3))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x4))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x5))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x6))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x7))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x8))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x9))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xA))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xB))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xC))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xD))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xE))},
{SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xF))},
{},
};
#ifdef CONFIG_PLD_SDIO_CNSS
struct cnss_sdio_wlan_driver pld_sdio_ops = {
.name = "pld_sdio",
.id_table = pld_sdio_id_table,
.probe = pld_sdio_probe,
.remove = pld_sdio_remove,
.reinit = pld_sdio_reinit,
.shutdown = pld_sdio_shutdown,
.crash_shutdown = pld_sdio_crash_shutdown,
#ifdef CONFIG_PM
.suspend = pld_sdio_suspend,
.resume = pld_sdio_resume,
#endif
};
/**
* pld_sdio_register_driver() - Register SDIO device callback functions
*
* Return: int
*/
int pld_sdio_register_driver(void)
{
return cnss_sdio_wlan_register_driver(&pld_sdio_ops);
}
/**
* pld_sdio_unregister_driver() - Unregister SDIO device callback functions
*
* Return: void
*/
void pld_sdio_unregister_driver(void)
{
cnss_sdio_wlan_unregister_driver(&pld_sdio_ops);
}
#else
struct sdio_driver pld_sdio_ops = {
.name = "pld_sdio",
.id_table = pld_sdio_id_table,
.probe = pld_sdio_probe,
.remove = pld_sdio_remove,
#ifdef CONFIG_PM
.suspend = pld_sdio_suspend,
.resume = pld_sdio_resume,
#endif
};
int pld_sdio_register_driver(void)
{
return sdio_register_driver(&pld_sdio_ops);
}
void pld_sdio_unregister_driver(void)
{
sdio_unregister_driver(&pld_sdio_ops);
}
#endif
#ifdef CONFIG_PLD_SDIO_CNSS
#ifdef CONFIG_TUFELLO_DUAL_FW_SUPPORT
static inline int pld_sdio_is_tufello_dual_fw_supported(void)
{
return 1;
}
#else
static inline int pld_sdio_is_tufello_dual_fw_supported(void)
{
return 0;
#endif
}
/**
* pld_sdio_get_fw_files_for_target() - Get FW file names
* @pfw_files: buffer for FW file names
* @target_type: target type
* @target_version: target version
*
* Return target specific FW file names to the buffer.
*
* Return: 0 for success
* Non zero failure code for errors
*/
int pld_sdio_get_fw_files_for_target(struct pld_fw_files *pfw_files,
u32 target_type, u32 target_version)
{
int ret = 0;
struct cnss_fw_files cnss_fw_files;
if (pfw_files == NULL)
return -ENODEV;
memset(pfw_files, 0, sizeof(*pfw_files));
if (target_version == PLD_QCA9377_REV1_1_VERSION) {
cnss_get_qca9377_fw_files(&cnss_fw_files, PLD_MAX_FILE_NAME,
pld_sdio_is_tufello_dual_fw_supported());
} else {
ret = cnss_get_fw_files_for_target(&cnss_fw_files,
target_type, target_version);
}
if (0 != ret)
return ret;
snprintf(pfw_files->image_file, PLD_MAX_FILE_NAME, PREFIX "%s",
cnss_fw_files.image_file);
snprintf(pfw_files->board_data, PLD_MAX_FILE_NAME, PREFIX "%s",
cnss_fw_files.board_data);
snprintf(pfw_files->otp_data, PLD_MAX_FILE_NAME, PREFIX "%s",
cnss_fw_files.otp_data);
snprintf(pfw_files->utf_file, PLD_MAX_FILE_NAME, PREFIX "%s",
cnss_fw_files.utf_file);
snprintf(pfw_files->utf_board_data, PLD_MAX_FILE_NAME, PREFIX "%s",
cnss_fw_files.utf_board_data);
snprintf(pfw_files->epping_file, PLD_MAX_FILE_NAME, PREFIX "%s",
cnss_fw_files.epping_file);
snprintf(pfw_files->evicted_data, PLD_MAX_FILE_NAME, PREFIX "%s",
cnss_fw_files.evicted_data);
return ret;
}
#else
#ifdef CONFIG_TUFELLO_DUAL_FW_SUPPORT
static inline void get_qca9377_fw_files(struct pld_fw_files *pfw_files,
u32 size)
{
memcpy(pfw_files, &fw_files_default, sizeof(*pfw_files));
}
#else
static inline void get_qca9377_fw_files(struct pld_fw_files *pfw_files,
u32 size)
{
memcpy(pfw_files, &fw_files_qca6174_fw_3_0, sizeof(*pfw_files));
}
#endif
int pld_sdio_get_fw_files_for_target(struct pld_fw_files *pfw_files,
u32 target_type, u32 target_version)
{
if (!pfw_files)
return -ENODEV;
switch (target_version) {
case PLD_AR6320_REV1_VERSION:
case PLD_AR6320_REV1_1_VERSION:
memcpy(pfw_files, &fw_files_qca6174_fw_1_1, sizeof(*pfw_files));
break;
case PLD_AR6320_REV1_3_VERSION:
memcpy(pfw_files, &fw_files_qca6174_fw_1_3, sizeof(*pfw_files));
break;
case PLD_AR6320_REV2_1_VERSION:
memcpy(pfw_files, &fw_files_qca6174_fw_2_0, sizeof(*pfw_files));
break;
case PLD_AR6320_REV3_VERSION:
case PLD_AR6320_REV3_2_VERSION:
memcpy(pfw_files, &fw_files_qca6174_fw_3_0, sizeof(*pfw_files));
break;
case PLD_QCA9377_REV1_1_VERSION:
get_qca9377_fw_files(pfw_files, sizeof(*pfw_files));
break;
default:
memcpy(pfw_files, &fw_files_default, sizeof(*pfw_files));
pr_err("%s version mismatch 0x%X ",
__func__, target_version);
break;
}
return 0;
}
#endif
#endif