blob: 153435837210fa82af70c839c34b9b4c08bff053 [file] [log] [blame]
/* Copyright (c) 2012, The Linux Foundation. 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 version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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/workqueue.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/bitops.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/ctype.h>
#include <linux/of_device.h>
#include <linux/msm_dsps.h>
#include <linux/uaccess.h>
#include <asm/mach-types.h>
#include <asm/arch_timer.h>
#include <mach/subsystem_restart.h>
#include <mach/ocmem.h>
#include <mach/msm_smd.h>
#include <mach/sensors_adsp.h>
#include <mach/msm_bus.h>
#include <mach/msm_bus_board.h>
#define DRV_NAME "sensors"
#define DRV_VERSION "1.00"
#define SNS_OCMEM_SMD_CHANNEL "SENSOR"
#define SNS_OCMEM_CLIENT_ID OCMEM_SENSORS
#define SNS_OCMEM_SIZE SZ_256K
#define SMD_BUF_SIZE 1024
#define SNS_TIMEOUT_MS 1000
#define SNS_OCMEM_ALLOC_GROW 0x00000001
#define SNS_OCMEM_ALLOC_SHRINK 0x00000002
#define SNS_OCMEM_MAP_DONE 0x00000004
#define SNS_OCMEM_MAP_FAIL 0x00000008
#define SNS_OCMEM_UNMAP_DONE 0x00000010
#define SNS_OCMEM_UNMAP_FAIL 0x00000020
#define DSPS_HAS_CLIENT 0x00000100
#define DSPS_HAS_NO_CLIENT 0x00000200
#define DSPS_BW_VOTE_ON 0x00000400
#define DSPS_BW_VOTE_OFF 0x00000800
#define DSPS_PHYS_ADDR_SET 0x00001000
/*
* Structure contains all state used by the sensors driver
*/
struct sns_adsp_control_s {
wait_queue_head_t sns_wait;
spinlock_t sns_lock;
struct workqueue_struct *sns_workqueue;
struct work_struct sns_work;
struct workqueue_struct *smd_wq;
struct work_struct smd_read_work;
smd_channel_t *smd_ch;
uint32_t sns_ocmem_status;
uint32_t mem_segments_size;
struct sns_mem_segment_s_v01 mem_segments[SNS_OCMEM_MAX_NUM_SEG_V01];
struct ocmem_buf *buf;
struct ocmem_map_list map_list;
struct ocmem_notifier *ocmem_handle;
bool ocmem_enabled;
struct notifier_block ocmem_nb;
uint32_t sns_ocmem_bus_client;
struct platform_device *pdev;
void *pil;
struct class *dev_class;
dev_t dev_num;
struct device *dev;
struct cdev *cdev;
};
static struct sns_adsp_control_s sns_ctl;
/*
* All asynchronous responses from the OCMEM driver are received
* by this function
*/
int sns_ocmem_drv_cb(struct notifier_block *self,
unsigned long action,
void *dev)
{
unsigned long flags;
spin_lock_irqsave(&sns_ctl.sns_lock, flags);
pr_debug("%s: Received OCMEM callback: action=%li\n",
__func__, action);
switch (action) {
case OCMEM_MAP_DONE:
sns_ctl.sns_ocmem_status |= SNS_OCMEM_MAP_DONE;
sns_ctl.sns_ocmem_status &= (~OCMEM_MAP_FAIL &
~SNS_OCMEM_UNMAP_DONE &
~SNS_OCMEM_UNMAP_FAIL);
break;
case OCMEM_MAP_FAIL:
sns_ctl.sns_ocmem_status |= SNS_OCMEM_MAP_FAIL;
sns_ctl.sns_ocmem_status &= (~OCMEM_MAP_DONE &
~SNS_OCMEM_UNMAP_DONE &
~SNS_OCMEM_UNMAP_FAIL);
break;
case OCMEM_UNMAP_DONE:
sns_ctl.sns_ocmem_status |= SNS_OCMEM_UNMAP_DONE;
sns_ctl.sns_ocmem_status &= (~SNS_OCMEM_UNMAP_FAIL &
~SNS_OCMEM_MAP_DONE &
~OCMEM_MAP_FAIL);
break;
case OCMEM_UNMAP_FAIL:
sns_ctl.sns_ocmem_status |= SNS_OCMEM_UNMAP_FAIL;
sns_ctl.sns_ocmem_status &= (~SNS_OCMEM_UNMAP_DONE &
~SNS_OCMEM_MAP_DONE &
~OCMEM_MAP_FAIL);
break;
case OCMEM_ALLOC_GROW:
sns_ctl.sns_ocmem_status |= SNS_OCMEM_ALLOC_GROW;
sns_ctl.sns_ocmem_status &= ~SNS_OCMEM_ALLOC_SHRINK;
break;
case OCMEM_ALLOC_SHRINK:
sns_ctl.sns_ocmem_status |= SNS_OCMEM_ALLOC_SHRINK;
sns_ctl.sns_ocmem_status &= ~SNS_OCMEM_ALLOC_GROW;
break;
default:
pr_err("%s: Unknown action received in OCMEM callback %lu\n",
__func__, action);
break;
}
spin_unlock_irqrestore(&sns_ctl.sns_lock, flags);
wake_up(&sns_ctl.sns_wait);
return 0;
}
/*
* Processes messages received through SMD from the ADSP
*
* @param hdr The message header
* @param msg Message pointer
*
*/
void sns_ocmem_smd_process(struct sns_ocmem_hdr_s *hdr, void *msg)
{
unsigned long flags;
spin_lock_irqsave(&sns_ctl.sns_lock, flags);
pr_debug("%s: Received message from ADSP; id: %i type: %i (%08x)\n",
__func__, hdr->msg_id, hdr->msg_type,
sns_ctl.sns_ocmem_status);
if (hdr->msg_id == SNS_OCMEM_PHYS_ADDR_RESP_V01 &&
hdr->msg_type == SNS_OCMEM_MSG_TYPE_RESP) {
struct sns_ocmem_phys_addr_resp_msg_v01 *msg_ptr =
(struct sns_ocmem_phys_addr_resp_msg_v01 *)msg;
pr_debug("%s: Received SNS_OCMEM_PHYS_ADDR_RESP_V01\n",
__func__);
pr_debug("%s: segments_valid=%d, segments_len=%d\n", __func__,
msg_ptr->segments_valid, msg_ptr->segments_len);
if (msg_ptr->segments_valid) {
sns_ctl.mem_segments_size = msg_ptr->segments_len;
memcpy(sns_ctl.mem_segments, msg_ptr->segments,
sizeof(struct sns_mem_segment_s_v01) *
msg_ptr->segments_len);
sns_ctl.sns_ocmem_status |= DSPS_PHYS_ADDR_SET;
} else {
pr_err("%s: Received invalid segment list\n", __func__);
}
} else if (hdr->msg_id == SNS_OCMEM_HAS_CLIENT_IND_V01 &&
hdr->msg_type == SNS_OCMEM_MSG_TYPE_IND) {
struct sns_ocmem_has_client_ind_msg_v01 *msg_ptr =
(struct sns_ocmem_has_client_ind_msg_v01 *)msg;
pr_debug("%s: Received SNS_OCMEM_HAS_CLIENT_IND_V01\n",
__func__);
pr_debug("%s: ADSP has %i client(s)\n", __func__,
msg_ptr->num_clients);
if (msg_ptr->num_clients > 0) {
sns_ctl.sns_ocmem_status |= DSPS_HAS_CLIENT;
sns_ctl.sns_ocmem_status &= ~DSPS_HAS_NO_CLIENT;
} else {
sns_ctl.sns_ocmem_status |= DSPS_HAS_NO_CLIENT;
sns_ctl.sns_ocmem_status &= ~DSPS_HAS_CLIENT;
}
} else if (hdr->msg_id == SNS_OCMEM_BW_VOTE_RESP_V01 &&
hdr->msg_type == SNS_OCMEM_MSG_TYPE_RESP) {
/* no need to handle this response msg, just return */
pr_debug("%s: Received SNS_OCMEM_BW_VOTE_RESP_V01\n", __func__);
spin_unlock_irqrestore(&sns_ctl.sns_lock, flags);
return;
} else if (hdr->msg_id == SNS_OCMEM_BW_VOTE_IND_V01 &&
hdr->msg_type == SNS_OCMEM_MSG_TYPE_IND) {
struct sns_ocmem_bw_vote_ind_msg_v01 *msg_ptr =
(struct sns_ocmem_bw_vote_ind_msg_v01 *)msg;
pr_debug("%s: Received BW_VOTE_IND_V01, is_vote_on=%d\n",
__func__, msg_ptr->is_vote_on);
if (msg_ptr->is_vote_on) {
sns_ctl.sns_ocmem_status |= DSPS_BW_VOTE_ON;
sns_ctl.sns_ocmem_status &= ~DSPS_BW_VOTE_OFF;
} else {
sns_ctl.sns_ocmem_status |= DSPS_BW_VOTE_OFF;
sns_ctl.sns_ocmem_status &= ~DSPS_BW_VOTE_ON;
}
} else {
pr_err("%s: Unknown message type received. id: %i; type: %i\n",
__func__, hdr->msg_id, hdr->msg_type);
}
spin_unlock_irqrestore(&sns_ctl.sns_lock, flags);
wake_up(&sns_ctl.sns_wait);
}
static void sns_ocmem_smd_read(struct work_struct *ws)
{
struct smd_channel *ch = sns_ctl.smd_ch;
unsigned char *buf = NULL;
int sz, len;
for (;;) {
sz = smd_cur_packet_size(ch);
BUG_ON(sz > SMD_BUF_SIZE);
len = smd_read_avail(ch);
pr_debug("%s: sz=%d, len=%d\n", __func__, sz, len);
if (len == 0 || len < sz)
break;
buf = kzalloc(SMD_BUF_SIZE, GFP_KERNEL);
if (buf == NULL) {
pr_err("%s: malloc failed", __func__);
break;
}
if (smd_read(ch, buf, sz) != sz) {
pr_err("%s: not enough data?!\n", __func__);
kfree(buf);
continue;
}
sns_ocmem_smd_process((struct sns_ocmem_hdr_s *)buf,
(void *)((char *)buf +
sizeof(struct sns_ocmem_hdr_s)));
kfree(buf);
}
}
/*
* All SMD notifications and messages from Sensors on ADSP are
* received by this function
*
*/
void sns_ocmem_smd_notify_data(void *data, unsigned int event)
{
if (event == SMD_EVENT_DATA) {
int sz;
pr_debug("%s: Received SMD event Data\n", __func__);
sz = smd_cur_packet_size(sns_ctl.smd_ch);
if ((sz > 0) && (sz <= smd_read_avail(sns_ctl.smd_ch)))
queue_work(sns_ctl.smd_wq, &sns_ctl.smd_read_work);
} else if (event == SMD_EVENT_OPEN) {
pr_debug("%s: Received SMD event Open\n", __func__);
} else if (event == SMD_EVENT_CLOSE) {
pr_debug("%s: Received SMD event Close\n", __func__);
}
}
static bool sns_ocmem_is_status_set(uint32_t sns_ocmem_status)
{
unsigned long flags;
bool is_set;
spin_lock_irqsave(&sns_ctl.sns_lock, flags);
is_set = sns_ctl.sns_ocmem_status & sns_ocmem_status;
spin_unlock_irqrestore(&sns_ctl.sns_lock, flags);
return is_set;
}
/*
* Wait for a response from ADSP or OCMEM Driver, timeout if necessary
*
* @param sns_ocmem_status Status flags to wait for.
* @param timeout_sec Seconds to wait before timeout
* @param timeout_nsec Nanoseconds to wait. Total timeout = nsec + sec
*
* @return 0 If any status flag is set at any time prior to a timeout.
* 0 if success or timedout ; <0 for failures
*/
static int sns_ocmem_wait(uint32_t sns_ocmem_status,
uint32_t timeout_ms)
{
int err;
if (timeout_ms) {
err = wait_event_interruptible_timeout(sns_ctl.sns_wait,
sns_ocmem_is_status_set(sns_ocmem_status),
msecs_to_jiffies(timeout_ms));
if (err == 0)
pr_err("%s: interruptible_timeout timeout err=%i\n",
__func__, err);
else if (err < 0)
pr_err("%s: interruptible_timeout failed err=%i\n",
__func__, err);
} else { /* no timeout */
err = wait_event_interruptible(sns_ctl.sns_wait,
sns_ocmem_is_status_set(sns_ocmem_status));
if (err < 0)
pr_err("%s: wait_event_interruptible failed err=%i\n",
__func__, err);
}
return err;
}
/*
* Sends a message to the ADSP via SMD.
*
* @param hdr Specifies message type and other meta data
* @param msg_ptr Pointer to the message contents.
* Must be freed within this function if no error is returned.
*
* @return 0 upon success; < 0 upon error
*/
static int
sns_ocmem_send_msg(struct sns_ocmem_hdr_s *hdr, void const *msg_ptr)
{
int rv = 0;
int err = 0;
void *temp = NULL;
int size = sizeof(struct sns_ocmem_hdr_s) + hdr->msg_size;
temp = kzalloc(sizeof(struct sns_ocmem_hdr_s) + hdr->msg_size,
GFP_KERNEL);
if (temp == NULL) {
pr_err("%s: allocation failure\n", __func__);
rv = -ENOMEM;
}
hdr->dst_module = SNS_OCMEM_MODULE_ADSP;
hdr->src_module = SNS_OCMEM_MODULE_KERNEL;
memcpy(temp, hdr, sizeof(struct sns_ocmem_hdr_s));
memcpy((char *)temp + sizeof(struct sns_ocmem_hdr_s),
msg_ptr, hdr->msg_size);
pr_debug("%s: send msg type: %i size: %i id: %i dst: %i src: %i\n",
__func__, hdr->msg_type, hdr->msg_size,
hdr->msg_id, hdr->dst_module, hdr->src_module);
if (hdr == NULL) {
pr_err("%s: NULL message header\n", __func__);
rv = -EINVAL;
} else {
if (sns_ctl.smd_ch == NULL) {
pr_err("%s: null smd_ch\n", __func__);
rv = -EINVAL;
}
err = smd_write(sns_ctl.smd_ch, temp, size);
if (err < 0) {
pr_err("%s: smd_write failed %i\n", __func__, err);
rv = -ECOMM;
} else {
pr_debug("%s smd_write successful ret=%d\n",
__func__, err);
}
}
kfree(temp);
return rv;
}
/*
* Load ADSP Firmware.
*/
static int sns_load_adsp(void)
{
sns_ctl.pil = subsystem_get("adsp");
if (IS_ERR(sns_ctl.pil)) {
pr_err("%s: fail to load ADSP firmware\n", __func__);
return -ENODEV;
}
pr_debug("%s: Q6/ADSP image is loaded\n", __func__);
return 0;
}
static int sns_ocmem_platform_data_populate(struct platform_device *pdev)
{
int ret;
struct msm_bus_scale_pdata *sns_ocmem_bus_scale_pdata = NULL;
struct msm_bus_vectors *sns_ocmem_bus_vectors = NULL;
struct msm_bus_paths *ocmem_sns_bus_paths = NULL;
u32 val;
if (!pdev->dev.of_node) {
pr_err("%s: device tree information missing\n", __func__);
return -ENODEV;
}
sns_ocmem_bus_vectors = kzalloc(sizeof(struct msm_bus_vectors),
GFP_KERNEL);
if (!sns_ocmem_bus_vectors) {
dev_err(&pdev->dev, "Failed to allocate memory for platform data\n");
return -ENOMEM;
}
ret = of_property_read_u32(pdev->dev.of_node,
"qcom,src-id", &val);
if (ret) {
dev_err(&pdev->dev, "%s: qcom,src-id missing in DT node\n",
__func__);
goto fail1;
}
sns_ocmem_bus_vectors->src = val;
ret = of_property_read_u32(pdev->dev.of_node,
"qcom,dst-id", &val);
if (ret) {
dev_err(&pdev->dev, "%s: qcom,dst-id missing in DT node\n",
__func__);
goto fail1;
}
sns_ocmem_bus_vectors->dst = val;
ret = of_property_read_u32(pdev->dev.of_node,
"qcom,ab", &val);
if (ret) {
dev_err(&pdev->dev, "%s: qcom,ab missing in DT node\n",
__func__);
goto fail1;
}
sns_ocmem_bus_vectors->ab = val;
ret = of_property_read_u32(pdev->dev.of_node,
"qcom,ib", &val);
if (ret) {
dev_err(&pdev->dev, "%s: qcom,ib missing in DT node\n",
__func__);
goto fail1;
}
sns_ocmem_bus_vectors->ib = val;
ocmem_sns_bus_paths = kzalloc(sizeof(struct msm_bus_paths),
GFP_KERNEL);
if (!ocmem_sns_bus_paths) {
dev_err(&pdev->dev, "Failed to allocate memory for platform data\n");
goto fail1;
}
ocmem_sns_bus_paths->num_paths = 1;
ocmem_sns_bus_paths->vectors = sns_ocmem_bus_vectors;
sns_ocmem_bus_scale_pdata =
kzalloc(sizeof(struct msm_bus_scale_pdata), GFP_KERNEL);
if (!sns_ocmem_bus_scale_pdata) {
dev_err(&pdev->dev, "Failed to allocate memory for platform data\n");
goto fail2;
}
sns_ocmem_bus_scale_pdata->usecase = ocmem_sns_bus_paths;
sns_ocmem_bus_scale_pdata->num_usecases = 1;
sns_ocmem_bus_scale_pdata->name = "sensors-ocmem";
dev_set_drvdata(&pdev->dev, sns_ocmem_bus_scale_pdata);
return ret;
fail2:
kfree(ocmem_sns_bus_paths);
fail1:
kfree(sns_ocmem_bus_vectors);
return ret;
}
/*
* Initialize all sensors ocmem driver data fields and register with the
* ocmem driver.
*
* @return 0 upon success; < 0 upon error
*/
static int sns_ocmem_init(void)
{
int i, err, ret;
struct sns_ocmem_hdr_s addr_req_hdr;
struct msm_bus_scale_pdata *sns_ocmem_bus_scale_pdata = NULL;
/* register from OCMEM callack */
sns_ctl.ocmem_handle =
ocmem_notifier_register(SNS_OCMEM_CLIENT_ID,
&sns_ctl.ocmem_nb);
if (sns_ctl.ocmem_handle == NULL) {
pr_err("OCMEM notifier registration failed\n");
return -EFAULT;
}
/* populate platform data */
ret = sns_ocmem_platform_data_populate(sns_ctl.pdev);
if (ret) {
dev_err(&sns_ctl.pdev->dev,
"%s: failed to populate platform data, rc = %d\n",
__func__, ret);
return -ENODEV;
}
sns_ocmem_bus_scale_pdata = dev_get_drvdata(&sns_ctl.pdev->dev);
sns_ctl.sns_ocmem_bus_client =
msm_bus_scale_register_client(sns_ocmem_bus_scale_pdata);
if (!sns_ctl.sns_ocmem_bus_client) {
pr_err("%s: msm_bus_scale_register_client() failed\n",
__func__);
return -EFAULT;
}
/* load ADSP first */
if (sns_load_adsp() != 0) {
pr_err("%s: sns_load_adsp failed\n", __func__);
return -EFAULT;
}
/*
* wait before open SMD channel from kernel to ensure
* channel has been openned already from ADSP side
*/
msleep(1000);
err = smd_named_open_on_edge(SNS_OCMEM_SMD_CHANNEL,
SMD_APPS_QDSP,
&sns_ctl.smd_ch,
NULL,
sns_ocmem_smd_notify_data);
if (err != 0) {
pr_err("%s: smd_named_open_on_edge failed %i\n", __func__, err);
return -EFAULT;
}
pr_debug("%s: SMD channel openned successfuly!\n", __func__);
/* wait for the channel ready before writing data */
msleep(1000);
addr_req_hdr.msg_id = SNS_OCMEM_PHYS_ADDR_REQ_V01;
addr_req_hdr.msg_type = SNS_OCMEM_MSG_TYPE_REQ;
addr_req_hdr.msg_size = 0;
err = sns_ocmem_send_msg(&addr_req_hdr, NULL);
if (err != 0) {
pr_err("%s: sns_ocmem_send_msg failed %i\n", __func__, err);
return -ECOMM;
}
err = sns_ocmem_wait(DSPS_PHYS_ADDR_SET, 0);
if (err != 0) {
pr_err("%s: sns_ocmem_wait failed %i\n", __func__, err);
return -EFAULT;
}
sns_ctl.map_list.num_chunks = sns_ctl.mem_segments_size;
for (i = 0; i < sns_ctl.mem_segments_size; i++) {
sns_ctl.map_list.chunks[i].ro =
sns_ctl.mem_segments[i].type == 1 ? true : false;
sns_ctl.map_list.chunks[i].ddr_paddr =
sns_ctl.mem_segments[i].start_address;
sns_ctl.map_list.chunks[i].size =
sns_ctl.mem_segments[i].size;
pr_debug("%s: chunks[%d]: ro=%d, ddr_paddr=0x%lx, size=%li",
__func__, i,
sns_ctl.map_list.chunks[i].ro,
sns_ctl.map_list.chunks[i].ddr_paddr,
sns_ctl.map_list.chunks[i].size);
}
return 0;
}
/*
* Unmaps memory in ocmem back to DDR, indicates to the ADSP its completion,
* and waits for it to finish removing its bandwidth vote.
*/
static void sns_ocmem_unmap(void)
{
unsigned long flags;
int err = 0;
ocmem_set_power_state(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf, OCMEM_ON);
spin_lock_irqsave(&sns_ctl.sns_lock, flags);
sns_ctl.sns_ocmem_status &= (~SNS_OCMEM_UNMAP_FAIL &
~SNS_OCMEM_UNMAP_DONE);
spin_unlock_irqrestore(&sns_ctl.sns_lock, flags);
err = ocmem_unmap(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf,
&sns_ctl.map_list);
if (err != 0) {
pr_err("ocmem_unmap failed %i\n", err);
} else {
err = sns_ocmem_wait(SNS_OCMEM_UNMAP_DONE |
SNS_OCMEM_UNMAP_FAIL, 0);
if (err == 0) {
if (sns_ocmem_is_status_set(SNS_OCMEM_UNMAP_DONE))
pr_debug("%s: OCMEM_UNMAP_DONE\n", __func__);
else if (sns_ocmem_is_status_set(
SNS_OCMEM_UNMAP_FAIL)) {
pr_err("%s: OCMEM_UNMAP_FAIL\n", __func__);
BUG_ON(true);
} else
pr_err("%s: status flag not set\n", __func__);
} else {
pr_err("%s: sns_ocmem_wait failed %i\n",
__func__, err);
}
}
ocmem_set_power_state(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf, OCMEM_OFF);
}
/*
* Waits for allocation to succeed. This may take considerable time if the device
* is presently in a high-power use case.
*
* @return 0 on success; < 0 upon error
*/
static int sns_ocmem_wait_for_alloc(void)
{
int err = 0;
err = sns_ocmem_wait(SNS_OCMEM_ALLOC_GROW |
DSPS_HAS_NO_CLIENT, 0);
if (err == 0) {
if (sns_ocmem_is_status_set(DSPS_HAS_NO_CLIENT)) {
pr_debug("%s: Lost client while waiting for GROW\n",
__func__);
ocmem_free(SNS_OCMEM_CLIENT_ID, sns_ctl.buf);
sns_ctl.buf = NULL;
return -EPIPE;
}
} else {
pr_err("sns_ocmem_wait failed %i\n", err);
return -EFAULT;
}
return 0;
}
/*
* Kicks-off the mapping of memory from DDR to ocmem. Waits for the process
* to complete, then indicates so to the ADSP.
*
* @return 0: Success; < 0: Other error
*/
static int sns_ocmem_map(void)
{
int err = 0;
unsigned long flags;
spin_lock_irqsave(&sns_ctl.sns_lock, flags);
sns_ctl.sns_ocmem_status &=
(~SNS_OCMEM_MAP_FAIL & ~SNS_OCMEM_MAP_DONE);
spin_unlock_irqrestore(&sns_ctl.sns_lock, flags);
/* vote for ocmem bus bandwidth */
err = msm_bus_scale_client_update_request(
sns_ctl.sns_ocmem_bus_client,
0);
if (err)
pr_err("%s: failed to vote for bus bandwidth\n", __func__);
err = ocmem_map(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf,
&sns_ctl.map_list);
if (err != 0) {
pr_debug("ocmem_map failed %i\n", err);
ocmem_set_power_state(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf, OCMEM_OFF);
ocmem_free(SNS_OCMEM_CLIENT_ID, sns_ctl.buf);
sns_ctl.buf = NULL;
} else {
err = sns_ocmem_wait(SNS_OCMEM_ALLOC_SHRINK |
DSPS_HAS_NO_CLIENT |
SNS_OCMEM_MAP_DONE |
SNS_OCMEM_MAP_FAIL, 0);
if (err == 0) {
if (sns_ocmem_is_status_set(SNS_OCMEM_MAP_DONE))
pr_debug("%s: OCMEM mapping DONE\n", __func__);
else if (sns_ocmem_is_status_set(DSPS_HAS_NO_CLIENT)) {
pr_debug("%s: Lost client while waiting for MAP\n",
__func__);
sns_ocmem_unmap();
ocmem_free(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf);
sns_ctl.buf = NULL;
err = -EPIPE;
} else if (sns_ocmem_is_status_set(
SNS_OCMEM_ALLOC_SHRINK)) {
pr_debug("%s: SHRINK while wait for MAP\n",
__func__);
sns_ocmem_unmap();
err = ocmem_shrink(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf, 0);
BUG_ON(err != 0);
err = -EFAULT;
} else if (sns_ocmem_is_status_set(
SNS_OCMEM_MAP_FAIL)) {
pr_err("%s: OCMEM mapping fails\n", __func__);
ocmem_set_power_state(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf,
OCMEM_OFF);
ocmem_free(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf);
sns_ctl.buf = NULL;
} else
pr_err("%s: status flag not set\n", __func__);
} else {
pr_err("sns_ocmem_wait failed %i\n", err);
}
}
return err;
}
/*
* Allocates memory in ocmem and maps to it from DDR.
*
* @return 0 upon success; <0 upon failure;
*/
static int sns_ocmem_alloc(void)
{
int err = 0;
unsigned long flags;
if (sns_ctl.buf == NULL) {
spin_lock_irqsave(&sns_ctl.sns_lock, flags);
sns_ctl.sns_ocmem_status &= ~SNS_OCMEM_ALLOC_GROW &
~SNS_OCMEM_ALLOC_SHRINK;
spin_unlock_irqrestore(&sns_ctl.sns_lock, flags);
sns_ctl.buf = ocmem_allocate_nb(SNS_OCMEM_CLIENT_ID,
SNS_OCMEM_SIZE);
if (sns_ctl.buf == NULL) {
pr_err("ocmem_allocate_nb returned NULL\n");
sns_ctl.ocmem_enabled = false;
err = -EFAULT;
} else if (sns_ctl.buf->len != 0 &&
SNS_OCMEM_SIZE > sns_ctl.buf->len) {
pr_err("ocmem_allocate_nb: invalid len %li, Req: %i)\n",
sns_ctl.buf->len, SNS_OCMEM_SIZE);
sns_ctl.ocmem_enabled = false;
err = -EFAULT;
}
}
pr_debug("%s OCMEM buf=%lx, buffer len=%li\n", __func__,
sns_ctl.buf->addr, sns_ctl.buf->len);
while (sns_ctl.ocmem_enabled) {
if (sns_ctl.buf->len == 0) {
pr_debug("%s: Waiting for memory allocation\n",
__func__);
err = sns_ocmem_wait_for_alloc();
if (err == -EPIPE) {
pr_debug("%s:Lost client while wait for alloc\n",
__func__);
break;
} else if (err != 0) {
pr_err("sns_ocmem_wait_for_alloc failed %i\n",
err);
break;
}
}
ocmem_set_power_state(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf,
OCMEM_ON);
err = sns_ocmem_map();
if (err == -EPIPE) {
pr_debug("%s: Lost client while waiting for mapping\n",
__func__);
break;
} else if (err < 0) {
pr_debug("%s: Mapping failed, will try again\n",
__func__);
break;
} else if (err == 0) {
pr_debug("%s: Mapping finished\n", __func__);
break;
}
}
return err;
}
/*
* Indicate to the ADSP that unmapping has completed, and wait for the response
* that its bandwidth vote has been removed.
*
* @return 0 Upon success; < 0 upon error
*/
static int sns_ocmem_unmap_send(void)
{
int err;
struct sns_ocmem_hdr_s msg_hdr;
struct sns_ocmem_bw_vote_req_msg_v01 msg;
memset(&msg, 0, sizeof(struct sns_ocmem_bw_vote_req_msg_v01));
msg_hdr.msg_id = SNS_OCMEM_BW_VOTE_REQ_V01;
msg_hdr.msg_type = SNS_OCMEM_MSG_TYPE_REQ;
msg_hdr.msg_size = sizeof(struct sns_ocmem_bw_vote_req_msg_v01);
msg.is_map = 0;
msg.vectors_valid = 0;
msg.vectors_len = 0;
pr_debug("%s: send bw_vote OFF\n", __func__);
err = sns_ocmem_send_msg(&msg_hdr, &msg);
if (err != 0) {
pr_err("%s: sns_ocmem_send_msg failed %i\n",
__func__, err);
} else {
err = sns_ocmem_wait(DSPS_BW_VOTE_OFF, 0);
if (err != 0)
pr_err("%s: sns_ocmem_wait failed %i\n", __func__, err);
}
return err;
}
/*
* Indicate to the ADSP that mapping has completed, and wait for the response
* that its bandwidth vote has been made.
*
* @return 0 Upon success; < 0 upon error
*/
static int sns_ocmem_map_send(void)
{
int err;
struct sns_ocmem_hdr_s msg_hdr;
struct sns_ocmem_bw_vote_req_msg_v01 msg;
struct ocmem_vectors *vectors;
memset(&msg, 0, sizeof(struct sns_ocmem_bw_vote_req_msg_v01));
msg_hdr.msg_id = SNS_OCMEM_BW_VOTE_REQ_V01;
msg_hdr.msg_type = SNS_OCMEM_MSG_TYPE_REQ;
msg_hdr.msg_size = sizeof(struct sns_ocmem_bw_vote_req_msg_v01);
msg.is_map = 1;
vectors = ocmem_get_vectors(SNS_OCMEM_CLIENT_ID, sns_ctl.buf);
if ((vectors != NULL)) {
memcpy(&msg.vectors, vectors, sizeof(vectors));
/* TODO: set vectors_len */
msg.vectors_valid = true;
msg.vectors_len = 0;
}
pr_debug("%s: send bw_vote ON\n", __func__);
err = sns_ocmem_send_msg(&msg_hdr, &msg);
if (err != 0) {
pr_err("%s: sns_ocmem_send_msg failed %i\n", __func__, err);
} else {
err = sns_ocmem_wait(DSPS_BW_VOTE_ON |
SNS_OCMEM_ALLOC_SHRINK, 0);
if (err != 0)
pr_err("%s: sns_ocmem_wait failed %i\n", __func__, err);
}
return err;
}
/*
* Perform the encessary operations to clean-up OCMEM after being notified that
* there is no longer a client; if sensors was evicted; or if some error
* has occurred.
*
* @param[i] do_free Whether the memory should be freed (true) or if shrink
* should be called instead (false).
*/
static void sns_ocmem_evicted(bool do_free)
{
int err = 0;
sns_ocmem_unmap();
if (do_free) {
ocmem_free(SNS_OCMEM_CLIENT_ID, sns_ctl.buf);
sns_ctl.buf = NULL;
} else {
err = ocmem_shrink(SNS_OCMEM_CLIENT_ID, sns_ctl.buf, 0);
BUG_ON(err != 0);
}
err = sns_ocmem_unmap_send();
if (err != 0)
pr_err("sns_ocmem_unmap_send failed %i\n", err);
}
/*
* After mapping has completed and the ADSP has reacted appropriately, wait
* for a shrink command or word from the ADSP that it no longer has a client.
*
* @return 0 If no clients; < 0 upon error;
*/
static int sns_ocmem_map_done(void)
{
int err = 0;
unsigned long flags;
err = sns_ocmem_map_send();
if (err != 0) {
pr_err("sns_ocmem_map_send failed %i\n", err);
sns_ocmem_evicted(true);
} else {
ocmem_set_power_state(SNS_OCMEM_CLIENT_ID,
sns_ctl.buf, OCMEM_OFF);
pr_debug("%s: Waiting for shrink or 'no client' updates\n",
__func__);
err = sns_ocmem_wait(DSPS_HAS_NO_CLIENT |
SNS_OCMEM_ALLOC_SHRINK, 0);
if (err == 0) {
if (sns_ocmem_is_status_set(DSPS_HAS_NO_CLIENT)) {
pr_debug("%s: No longer have a client\n",
__func__);
sns_ocmem_evicted(true);
} else if (sns_ocmem_is_status_set(
SNS_OCMEM_ALLOC_SHRINK)) {
pr_debug("%s: Received SHRINK\n", __func__);
sns_ocmem_evicted(false);
spin_lock_irqsave(&sns_ctl.sns_lock, flags);
sns_ctl.sns_ocmem_status &=
~SNS_OCMEM_ALLOC_SHRINK;
spin_unlock_irqrestore(&sns_ctl.sns_lock,
flags);
err = -EFAULT;
}
} else {
pr_err("sns_ocmem_wait failed %i\n", err);
}
}
return err;
}
/*
* Main function.
* Initializes sensors ocmem feature, and waits for an ADSP client.
*/
static void sns_ocmem_main(struct work_struct *work)
{
int err = 0;
pr_debug("%s\n", __func__);
err = sns_ocmem_init();
if (err != 0) {
pr_err("%s: sns_ocmem_init failed %i\n", __func__, err);
return;
}
while (true) {
pr_debug("%s: Waiting for sensor client\n", __func__);
if (sns_ocmem_is_status_set(DSPS_HAS_CLIENT) ||
!sns_ocmem_wait(DSPS_HAS_CLIENT, 0)) {
pr_debug("%s: DSPS_HAS_CLIENT\n", __func__);
err = sns_ocmem_alloc();
if (err != 0) {
pr_err("sns_ocmem_alloc failed %i\n", err);
return;
} else {
err = sns_ocmem_map_done();
if (err != 0) {
pr_err("sns_ocmem_map_done failed %i",
err);
return;
}
}
}
}
ocmem_notifier_unregister(sns_ctl.ocmem_handle,
&sns_ctl.ocmem_nb);
}
static int sensors_adsp_open(struct inode *ip, struct file *fp)
{
int ret = 0;
return ret;
}
static int sensors_adsp_release(struct inode *inode, struct file *file)
{
return 0;
}
/*
* Read QTimer clock ticks and scale down to 32KHz clock as used
* in DSPS
*/
static u32 sns_read_qtimer(void)
{
u64 val;
val = arch_counter_get_cntpct();
/*
* To convert ticks from 19.2 Mhz clock to 32768 Hz clock:
* x = (value * 32768) / 19200000
* This is same as first left shift the value by 4 bits, i.e. mutiply
* by 16, and then divide by 9375. The latter is preferable since
* QTimer tick (value) is 56-bit, so (value * 32768) could overflow,
* while (value * 16) will never do
*/
val <<= 4;
do_div(val, 9375);
return (u32)val;
}
/*
* IO Control - handle commands from client.
*/
static long sensors_adsp_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
int ret = 0;
u32 val = 0;
switch (cmd) {
case DSPS_IOCTL_READ_SLOW_TIMER:
val = sns_read_qtimer();
ret = put_user(val, (u32 __user *) arg);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
/*
* platform driver
*/
const struct file_operations sensors_adsp_fops = {
.owner = THIS_MODULE,
.open = sensors_adsp_open,
.release = sensors_adsp_release,
.unlocked_ioctl = sensors_adsp_ioctl,
};
static int sensors_adsp_probe(struct platform_device *pdev)
{
int ret = 0;
sns_ctl.dev_class = class_create(THIS_MODULE, DRV_NAME);
if (sns_ctl.dev_class == NULL) {
pr_err("%s: class_create fail.\n", __func__);
goto res_err;
}
ret = alloc_chrdev_region(&sns_ctl.dev_num, 0, 1, DRV_NAME);
if (ret) {
pr_err("%s: alloc_chrdev_region fail.\n", __func__);
goto alloc_chrdev_region_err;
}
sns_ctl.dev = device_create(sns_ctl.dev_class, NULL,
sns_ctl.dev_num,
&sns_ctl, DRV_NAME);
if (IS_ERR(sns_ctl.dev)) {
pr_err("%s: device_create fail.\n", __func__);
goto device_create_err;
}
sns_ctl.cdev = cdev_alloc();
if (sns_ctl.cdev == NULL) {
pr_err("%s: cdev_alloc fail.\n", __func__);
goto cdev_alloc_err;
}
cdev_init(sns_ctl.cdev, &sensors_adsp_fops);
sns_ctl.cdev->owner = THIS_MODULE;
ret = cdev_add(sns_ctl.cdev, sns_ctl.dev_num, 1);
if (ret) {
pr_err("%s: cdev_add fail.\n", __func__);
goto cdev_add_err;
}
sns_ctl.sns_workqueue =
alloc_workqueue("sns_ocmem", WQ_NON_REENTRANT, 0);
if (!sns_ctl.sns_workqueue) {
pr_err("%s: Failed to create work queue\n",
__func__);
goto cdev_add_err;
}
sns_ctl.smd_wq =
alloc_workqueue("smd_wq", WQ_NON_REENTRANT, 0);
if (!sns_ctl.smd_wq) {
pr_err("%s: Failed to create work queue\n",
__func__);
goto cdev_add_err;
}
init_waitqueue_head(&sns_ctl.sns_wait);
spin_lock_init(&sns_ctl.sns_lock);
sns_ctl.ocmem_handle = NULL;
sns_ctl.buf = NULL;
sns_ctl.sns_ocmem_status = 0;
sns_ctl.ocmem_enabled = true;
sns_ctl.ocmem_nb.notifier_call = sns_ocmem_drv_cb;
sns_ctl.smd_ch = NULL;
sns_ctl.pdev = pdev;
INIT_WORK(&sns_ctl.sns_work, sns_ocmem_main);
INIT_WORK(&sns_ctl.smd_read_work, sns_ocmem_smd_read);
queue_work(sns_ctl.sns_workqueue, &sns_ctl.sns_work);
return 0;
cdev_add_err:
kfree(sns_ctl.cdev);
cdev_alloc_err:
device_destroy(sns_ctl.dev_class, sns_ctl.dev_num);
device_create_err:
unregister_chrdev_region(sns_ctl.dev_num, 1);
alloc_chrdev_region_err:
class_destroy(sns_ctl.dev_class);
res_err:
return -ENODEV;
}
static int sensors_adsp_remove(struct platform_device *pdev)
{
struct msm_bus_scale_pdata *sns_ocmem_bus_scale_pdata = NULL;
sns_ocmem_bus_scale_pdata = (struct msm_bus_scale_pdata *)
dev_get_drvdata(&pdev->dev);
kfree(sns_ocmem_bus_scale_pdata->usecase->vectors);
kfree(sns_ocmem_bus_scale_pdata->usecase);
kfree(sns_ocmem_bus_scale_pdata);
ocmem_notifier_unregister(sns_ctl.ocmem_handle,
&sns_ctl.ocmem_nb);
destroy_workqueue(sns_ctl.sns_workqueue);
destroy_workqueue(sns_ctl.smd_wq);
cdev_del(sns_ctl.cdev);
kfree(sns_ctl.cdev);
sns_ctl.cdev = NULL;
device_destroy(sns_ctl.dev_class, sns_ctl.dev_num);
unregister_chrdev_region(sns_ctl.dev_num, 1);
class_destroy(sns_ctl.dev_class);
return 0;
}
static const struct of_device_id msm_adsp_sensors_dt_match[] = {
{.compatible = "qcom,msm-adsp-sensors"},
{}
};
MODULE_DEVICE_TABLE(of, msm_adsp_sensors_dt_match);
static struct platform_driver sensors_adsp_driver = {
.driver = {
.name = "sensors-adsp",
.owner = THIS_MODULE,
.of_match_table = msm_adsp_sensors_dt_match,
},
.probe = sensors_adsp_probe,
.remove = sensors_adsp_remove,
};
/*
* Module Init.
*/
static int sensors_adsp_init(void)
{
int rc;
pr_debug("%s driver version %s.\n", DRV_NAME, DRV_VERSION);
rc = platform_driver_register(&sensors_adsp_driver);
if (rc) {
pr_err("%s: Failed to register sensors adsp driver\n",
__func__);
return rc;
}
return 0;
}
/*
* Module Exit.
*/
static void sensors_adsp_exit(void)
{
platform_driver_unregister(&sensors_adsp_driver);
}
module_init(sensors_adsp_init);
module_exit(sensors_adsp_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Sensors ADSP driver");