/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");