blob: 44c78738b4b5e5be1b3b4341646f919ea27ec251 [file] [log] [blame]
/*
* drivers/amlogic/mailbox/scpi_protocol.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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
* (at your option) 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.
*
*/
#include <linux/err.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/mailbox_client.h>
#include <linux/amlogic/scpi_protocol.h>
#include <linux/slab.h>
#include "meson_mhu.h"
#define CMD_ID_SHIFT 0
#define CMD_ID_MASK 0xff
#define CMD_SENDER_ID_SHIFT 8
#define CMD_SENDER_ID_MASK 0xff
#define CMD_DATA_SIZE_SHIFT 20
#define CMD_DATA_SIZE_MASK 0x1ff
#define PACK_SCPI_CMD(cmd, sender, txsz) \
((((cmd) & CMD_ID_MASK) << CMD_ID_SHIFT) | \
(((sender) & CMD_SENDER_ID_MASK) << CMD_SENDER_ID_SHIFT) | \
(((txsz) & CMD_DATA_SIZE_MASK) << CMD_DATA_SIZE_SHIFT))
#define MAX_DVFS_DOMAINS 3
#define MAX_DVFS_OPPS 16
#define MAX_EFUSE_INFO 12
#define MAX_CHIPVER_INFO 4
#define DVFS_LATENCY(hdr) ((hdr) >> 16)
#define DVFS_OPP_COUNT(hdr) (((hdr) >> 8) & 0xff)
enum scpi_error_codes {
SCPI_SUCCESS = 0, /* Success */
SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */
SCPI_ERR_ALIGN = 2, /* Invalid alignment */
SCPI_ERR_SIZE = 3, /* Invalid size */
SCPI_ERR_HANDLER = 4, /* Invalid handler/callback */
SCPI_ERR_ACCESS = 5, /* Invalid access/permission denied */
SCPI_ERR_RANGE = 6, /* Value out of range */
SCPI_ERR_TIMEOUT = 7, /* Timeout has occurred */
SCPI_ERR_NOMEM = 8, /* Invalid memory area or pointer */
SCPI_ERR_PWRSTATE = 9, /* Invalid power state */
SCPI_ERR_SUPPORT = 10, /* Not supported or disabled */
SCPI_ERR_DEVICE = 11, /* Device error */
SCPI_ERR_MAX
};
static int scpi_freq_map_table[] = {
0,
0,
1200,
1300,
1400,
1500,
1600,
1700,
1800,
1900,
2000,
2100,
2200,
2300,
2400,
0
};
static int scpi_volt_map_table[] = {
0,
0,
900,
910,
920,
930,
940,
950,
960,
970,
980,
990,
1000,
1010,
1020,
0
};
struct scpi_data_buf {
int client_id;
struct mhu_data_buf *data;
struct completion complete;
};
static int high_priority_cmds[] = {
SCPI_CMD_GET_CSS_PWR_STATE,
SCPI_CMD_CFG_PWR_STATE_STAT,
SCPI_CMD_GET_PWR_STATE_STAT,
SCPI_CMD_SET_DVFS,
SCPI_CMD_GET_DVFS,
SCPI_CMD_SET_RTC,
SCPI_CMD_GET_RTC,
SCPI_CMD_SET_CLOCK_INDEX,
SCPI_CMD_SET_CLOCK_VALUE,
SCPI_CMD_GET_CLOCK_VALUE,
SCPI_CMD_SET_PSU,
SCPI_CMD_GET_PSU,
SCPI_CMD_SENSOR_CFG_PERIODIC,
SCPI_CMD_SENSOR_CFG_BOUNDS,
SCPI_CMD_WAKEUP_REASON_GET,
SCPI_CMD_WAKEUP_REASON_CLR,
};
static struct scpi_dvfs_info *scpi_opps[MAX_DVFS_DOMAINS];
static int scpi_linux_errmap[SCPI_ERR_MAX] = {
0, -EINVAL, -ENOEXEC, -EMSGSIZE,
-EINVAL, -EACCES, -ERANGE, -ETIMEDOUT,
-ENOMEM, -EINVAL, -EOPNOTSUPP, -EIO,
};
static inline int scpi_to_linux_errno(int errno)
{
if (errno >= SCPI_SUCCESS && errno < SCPI_ERR_MAX)
return scpi_linux_errmap[errno];
return -EIO;
}
static bool high_priority_chan_supported(int cmd)
{
int idx;
for (idx = 0; idx < ARRAY_SIZE(high_priority_cmds); idx++)
if (cmd == high_priority_cmds[idx])
return true;
return false;
}
static void scpi_rx_callback(struct mbox_client *cl, void *msg)
{
struct mhu_data_buf *data = (struct mhu_data_buf *)msg;
struct scpi_data_buf *scpi_buf = data->cl_data;
complete(&scpi_buf->complete);
}
static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, bool high_priority)
{
struct mbox_chan *chan;
struct mbox_client cl = {0};
struct mhu_data_buf *data = scpi_buf->data;
u32 status;
cl.dev = the_scpi_device;
cl.rx_callback = scpi_rx_callback;
chan = mbox_request_channel(&cl, high_priority);
if (IS_ERR(chan))
return PTR_ERR(chan);
init_completion(&scpi_buf->complete);
if (mbox_send_message(chan, (void *)data) < 0) {
status = SCPI_ERR_TIMEOUT;
goto free_channel;
}
wait_for_completion(&scpi_buf->complete);
status = *(u32 *)(data->rx_buf); /* read first word */
free_channel:
mbox_free_channel(chan);
return scpi_to_linux_errno(status);
}
#define SCPI_SETUP_DBUF(scpi_buf, mhu_buf, _client_id,\
_cmd, _tx_buf, _rx_buf) \
do { \
struct mhu_data_buf *pdata = &mhu_buf; \
pdata->cmd = _cmd; \
pdata->tx_buf = &_tx_buf; \
pdata->tx_size = sizeof(_tx_buf); \
pdata->rx_buf = &_rx_buf; \
pdata->rx_size = sizeof(_rx_buf); \
scpi_buf.client_id = _client_id; \
scpi_buf.data = pdata; \
} while (0)
#define SCPI_SETUP_DBUF_SIZE(scpi_buf, mhu_buf, _client_id,\
_cmd, _tx_buf, _tx_size, _rx_buf, _rx_size) \
do { \
struct mhu_data_buf *pdata = &mhu_buf; \
pdata->cmd = _cmd; \
pdata->tx_buf = _tx_buf; \
pdata->tx_size = _tx_size; \
pdata->rx_buf = _rx_buf; \
pdata->rx_size = _rx_size; \
scpi_buf.client_id = _client_id; \
scpi_buf.data = pdata; \
} while (0)
static int scpi_execute_cmd(struct scpi_data_buf *scpi_buf)
{
struct mhu_data_buf *data;
bool high_priority;
if (!scpi_buf || !scpi_buf->data)
return -EINVAL;
data = scpi_buf->data;
high_priority = high_priority_chan_supported(data->cmd);
data->cmd = PACK_SCPI_CMD(data->cmd, scpi_buf->client_id,
data->tx_size);
data->cl_data = scpi_buf;
return send_scpi_cmd(scpi_buf, high_priority);
}
unsigned long scpi_clk_get_val(u16 clk_id)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u32 clk_rate;
} buf;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_CLOCKS,
SCPI_CMD_GET_CLOCK_VALUE, clk_id, buf);
if (scpi_execute_cmd(&sdata))
return 0;
return buf.clk_rate;
}
EXPORT_SYMBOL_GPL(scpi_clk_get_val);
int scpi_clk_set_val(u16 clk_id, unsigned long rate)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
int stat;
struct __packed {
u32 clk_rate;
u16 clk_id;
} buf;
buf.clk_rate = (u32)rate;
buf.clk_id = clk_id;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_CLOCKS,
SCPI_CMD_SET_CLOCK_VALUE, buf, stat);
return scpi_execute_cmd(&sdata);
}
EXPORT_SYMBOL_GPL(scpi_clk_set_val);
struct scpi_dvfs_info *scpi_dvfs_get_opps(u8 domain)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u32 header;
struct scpi_opp_entry opp[MAX_DVFS_OPPS];
} buf;
struct scpi_dvfs_info *opps;
size_t opps_sz;
int count, ret;
if (domain >= MAX_DVFS_DOMAINS)
return ERR_PTR(-EINVAL);
if (scpi_opps[domain]) /* data already populated */
return scpi_opps[domain];
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_DVFS,
SCPI_CMD_GET_DVFS_INFO, domain, buf);
ret = scpi_execute_cmd(&sdata);
if (ret)
return ERR_PTR(ret);
opps = kmalloc(sizeof(*opps), GFP_KERNEL);
if (!opps)
return ERR_PTR(-ENOMEM);
count = DVFS_OPP_COUNT(buf.header);
opps_sz = count * sizeof(*(opps->opp));
opps->count = count;
opps->latency = DVFS_LATENCY(buf.header);
opps->opp = kmalloc(opps_sz, GFP_KERNEL);
if (!opps->opp) {
kfree(opps);
return ERR_PTR(-ENOMEM);
}
memcpy(opps->opp, &buf.opp[0], opps_sz);
scpi_opps[domain] = opps;
return opps;
}
EXPORT_SYMBOL_GPL(scpi_dvfs_get_opps);
int scpi_dvfs_get_idx(u8 domain)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u8 dvfs_idx;
} buf;
int ret;
if (domain >= MAX_DVFS_DOMAINS)
return -EINVAL;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_DVFS,
SCPI_CMD_GET_DVFS, domain, buf);
ret = scpi_execute_cmd(&sdata);
if (!ret)
ret = buf.dvfs_idx;
return ret;
}
EXPORT_SYMBOL_GPL(scpi_dvfs_get_idx);
int scpi_dvfs_set_idx(u8 domain, u8 idx)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u8 dvfs_domain;
u8 dvfs_idx;
} buf;
int stat;
buf.dvfs_idx = idx;
buf.dvfs_domain = domain;
if (domain >= MAX_DVFS_DOMAINS)
return -EINVAL;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_DVFS,
SCPI_CMD_SET_DVFS, buf, stat);
return scpi_execute_cmd(&sdata);
}
EXPORT_SYMBOL_GPL(scpi_dvfs_set_idx);
int scpi_get_sensor(char *name)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u16 sensors;
} cap_buf;
struct __packed {
u32 status;
u16 sensor;
u8 class;
u8 trigger;
char name[20];
} info_buf;
int ret;
u16 sensor_id;
/* This should be handled by a generic macro */
do {
struct mhu_data_buf *pdata = &mdata;
pdata->cmd = SCPI_CMD_SENSOR_CAPABILITIES;
pdata->tx_size = 0;
pdata->rx_buf = &cap_buf;
pdata->rx_size = sizeof(cap_buf);
sdata.client_id = SCPI_CL_THERMAL;
sdata.data = pdata;
} while (0);
ret = scpi_execute_cmd(&sdata);
if (ret)
goto out;
ret = -ENODEV;
for (sensor_id = 0; sensor_id < cap_buf.sensors; sensor_id++) {
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_THERMAL,
SCPI_CMD_SENSOR_INFO, sensor_id, info_buf);
ret = scpi_execute_cmd(&sdata);
if (ret)
break;
if (!strcmp(name, info_buf.name)) {
ret = sensor_id;
break;
}
}
out:
return ret;
}
EXPORT_SYMBOL_GPL(scpi_get_sensor);
int scpi_get_sensor_value(u16 sensor, u32 *val)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u32 val;
} buf;
int ret;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_THERMAL, SCPI_CMD_SENSOR_VALUE,
sensor, buf);
ret = scpi_execute_cmd(&sdata);
if (ret == 0)
*val = buf.val;
return ret;
}
EXPORT_SYMBOL_GPL(scpi_get_sensor_value);
/****Send fail when data size > 0x1fd. ***
* Because of USER_LOW_TASK_SHARE_MEM_BASE ***
* size limitation.
* You can call scpi_send_usr_data()
* multi-times when your data is bigger
* than 0x1fe
*/
int scpi_send_usr_data(u32 client_id, u32 *val, u32 size)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u32 val;
} buf;
int ret;
/*client_id bit map should locates @ 0xff.
* bl30 will send client_id via half-Word
*/
if (client_id & ~0xff)
return -E2BIG;
/*Check size here because of USER_LOW_TASK_SHARE_MEM_BASE
* size limitation, and first Word is used as command,
* second word is used as tx_size.
*/
if (size > 0x1fd)
return -EPERM;
SCPI_SETUP_DBUF_SIZE(sdata, mdata, client_id, SCPI_CMD_SET_USR_DATA,
val, size, &buf, sizeof(buf));
ret = scpi_execute_cmd(&sdata);
return ret;
}
EXPORT_SYMBOL_GPL(scpi_send_usr_data);
int scpi_get_vrtc(u32 *p_vrtc)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
u32 temp = 0;
struct __packed {
u32 status;
u32 vrtc;
} buf;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE,
SCPI_CMD_GET_RTC, temp, buf);
if (scpi_execute_cmd(&sdata))
return -EPERM;
*p_vrtc = buf.vrtc;
return 0;
}
EXPORT_SYMBOL_GPL(scpi_get_vrtc);
int scpi_set_vrtc(u32 vrtc_val)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
int state;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE,
SCPI_CMD_SET_RTC, vrtc_val, state);
if (scpi_execute_cmd(&sdata))
return -EPERM;
return 0;
}
EXPORT_SYMBOL_GPL(scpi_set_vrtc);
int scpi_get_ring_value(unsigned char *val)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
unsigned int status;
unsigned char ringinfo[MAX_EFUSE_INFO];
} buf;
int ret;
u32 temp = 0;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE, SCPI_CMD_OSCRING_VALUE,
temp, buf);
ret = scpi_execute_cmd(&sdata);
if (ret == 0)
memcpy(val, &buf.ringinfo, sizeof(buf.ringinfo));
return ret;
}
EXPORT_SYMBOL_GPL(scpi_get_ring_value);
int scpi_get_wakeup_reason(u32 *wakeup_reason)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
u32 temp = 0;
struct __packed {
u32 status;
u32 reason;
} buf;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE,
SCPI_CMD_WAKEUP_REASON_GET, temp, buf);
if (scpi_execute_cmd(&sdata))
return -EPERM;
*wakeup_reason = buf.reason;
return 0;
}
EXPORT_SYMBOL_GPL(scpi_get_wakeup_reason);
int scpi_clr_wakeup_reason(void)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
u32 temp = 0, state;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE,
SCPI_CMD_WAKEUP_REASON_CLR, temp, state);
if (scpi_execute_cmd(&sdata))
return -EPERM;
return 0;
}
EXPORT_SYMBOL_GPL(scpi_clr_wakeup_reason);
int scpi_get_cec_val(enum scpi_std_cmd index, u32 *p_cec)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
u32 temp = 0;
struct __packed {
u32 status;
u32 cec_val;
} buf;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE,
index, temp, buf);
if (scpi_execute_cmd(&sdata))
return -EPERM;
*p_cec = buf.cec_val;
return 0;
}
EXPORT_SYMBOL_GPL(scpi_get_cec_val);
int scpi_get_chipver_value(unsigned char *val)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
unsigned int status;
unsigned char chipverinfo[MAX_CHIPVER_INFO];
} buf;
int ret;
u32 temp = 0;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE, SCPI_CMD_CHIPVER_VALUE,
temp, buf);
ret = scpi_execute_cmd(&sdata);
if (ret == 0)
memcpy(val, &buf.chipverinfo, sizeof(buf.chipverinfo));
return ret;
}
EXPORT_SYMBOL_GPL(scpi_get_chipver_value);
u8 scpi_get_ethernet_calc(void)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
u8 temp = 0;
struct __packed {
u32 status;
u8 eth_calc;
} buf;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE,
SCPI_CMD_GET_ETHERNET_CALC, temp, buf);
if (scpi_execute_cmd(&sdata))
return -EPERM;
return buf.eth_calc;
}
EXPORT_SYMBOL_GPL(scpi_get_ethernet_calc);
int scpi_get_cpuinfo(enum scpi_get_pfm_type type, u32 *freq, u32 *vol)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
u8 index = 0;
int ret;
struct __packed {
u32 status;
u8 pfm_info[4];
} buf;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_NONE,
SCPI_CMD_GET_CPUINFO, index, buf);
if (scpi_execute_cmd(&sdata))
return -EPERM;
switch (type) {
case SCPI_CPUINFO_VERSION:
ret = buf.pfm_info[0];
break;
case SCPI_CPUINFO_CLUSTER0:
index = (buf.pfm_info[1] >> 4) & 0xf;
*freq = scpi_freq_map_table[index];
index = buf.pfm_info[1] & 0xf;
*vol = scpi_volt_map_table[index];
ret = 0;
break;
case SCPI_CPUINFO_CLUSTER1:
index = (buf.pfm_info[2] >> 4) & 0xf;
*freq = scpi_freq_map_table[index];
index = buf.pfm_info[2] & 0xf;
*vol = scpi_volt_map_table[index];
ret = 0;
break;
case SCPI_CPUINFO_SLT:
index = (buf.pfm_info[3] >> 4) & 0xf;
*freq = scpi_freq_map_table[index];
index = buf.pfm_info[3] & 0xf;
*vol = scpi_volt_map_table[index];
ret = 0;
break;
default:
*freq = 0;
*vol = 0;
ret = -1;
break;
};
return ret;
}
EXPORT_SYMBOL_GPL(scpi_get_cpuinfo);