1235 lines
31 KiB
C
1235 lines
31 KiB
C
/* 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");
|