// SPDX-License-Identifier: GPL-2.0 /* * Synaptics TouchCom touchscreen driver * * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. * DOLLARS. */ /** * @file syna_tcm2_platform_spi.c * * This file is the reference code of I2C module used for communicating with * Synaptics TouchCom device using I2C */ #include #include #include "syna_tcm2.h" #include "syna_tcm2_platform.h" #define SPI_MODULE_NAME "synaptics_tcm_spi" static unsigned char *buf; static unsigned int buf_size; static struct spi_transfer *xfer; static struct platform_device *syna_spi_device; /** * syna_request_managed_device() * * Request and return the device pointer for managed * * @param * none. * * @return * a device pointer allocated previously */ #if defined(DEV_MANAGED_API) || defined(USE_DRM_PANEL_NOTIFIER) struct device *syna_request_managed_device(void) { if (!syna_spi_device) return NULL; return syna_spi_device->dev.parent; } #endif /** * syna_spi_hw_reset() * * Toggle the hardware gpio pin to perform the chip reset * * @param * [ in] hw_if: the handle of hw interface * * @return * none. */ static void syna_spi_hw_reset(struct syna_hw_interface *hw_if) { struct syna_hw_rst_data *rst = &hw_if->bdata_rst; LOGI("Trigger hardware reset.\n"); if (rst->reset_gpio >= 0) { gpio_set_value(rst->reset_gpio, rst->reset_on_state); syna_pal_sleep_ms(rst->reset_active_ms); gpio_set_value(rst->reset_gpio, !rst->reset_on_state); syna_pal_sleep_ms(rst->reset_delay_ms); } } /** * syna_spi_request_gpio() * * Setup the given gpio * * @param * [ in] gpio: the target gpio * [ in] config: '1' for setting up, and '0' to release the gpio * [ in] dir: default direction of gpio * [ in] state: default state of gpio * * @return * on success, 0; otherwise, negative value on error. */ static int syna_spi_request_gpio(int gpio, bool config, int dir, int state, char *label) { int retval; #ifdef DEV_MANAGED_API struct device *dev = syna_request_managed_device(); if (!dev) { LOGE("Invalid managed device\n"); return -ENODEV; } #endif if (config) { retval = scnprintf(label, 16, "tcm_gpio_%d\n", gpio); if (retval < 0) { LOGE("Fail to set GPIO label\n"); return retval; } #ifdef DEV_MANAGED_API retval = devm_gpio_request(dev, gpio, label); #else /* Legacy API */ retval = gpio_request(gpio, label); #endif if (retval < 0) { LOGE("Fail to request GPIO %d\n", gpio); return retval; } if (dir == 0) retval = gpio_direction_input(gpio); else retval = gpio_direction_output(gpio, state); if (retval < 0) { LOGE("Fail to set GPIO %d direction\n", gpio); return retval; } } else { #ifdef DEV_MANAGED_API devm_gpio_free(dev, gpio); #else /* Legacy API */ gpio_free(gpio); #endif } return 0; } /** * syna_spi_config_gpio() * * Initialize the GPIOs defined in device tree * * @param * [ in] hw_if: the handle of hw interface * * @return * on success, 0; otherwise, negative value on error. */ static int syna_spi_config_gpio(struct syna_hw_interface *hw_if) { int retval; static char str_irq_gpio[32] = {0}; static char str_rst_gpio[32] = {0}; static char str_vdd_gpio[32] = {0}; static char str_avdd_gpio[32] = {0}; struct syna_hw_attn_data *attn = &hw_if->bdata_attn; struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; struct syna_hw_rst_data *rst = &hw_if->bdata_rst; if (attn->irq_gpio >= 0) { retval = syna_spi_request_gpio(attn->irq_gpio, true, 0, 0, str_irq_gpio); if (retval < 0) { LOGE("Fail to configure interrupt GPIO %d\n", attn->irq_gpio); goto err_set_gpio_irq; } } if (rst->reset_gpio >= 0) { retval = syna_spi_request_gpio(rst->reset_gpio, true, 1, !rst->reset_on_state, str_rst_gpio); if (retval < 0) { LOGE("Fail to configure reset GPIO %d\n", rst->reset_gpio); goto err_set_gpio_reset; } } if (pwr->vdd_gpio >= 0) { retval = syna_spi_request_gpio(pwr->vdd_gpio, true, 1, !pwr->power_on_state, str_vdd_gpio); if (retval < 0) { LOGE("Fail to configure vdd GPIO %d\n", pwr->vdd_gpio); goto err_set_gpio_vdd; } } if (pwr->avdd_gpio >= 0) { retval = syna_spi_request_gpio(pwr->avdd_gpio, true, 1, !pwr->power_on_state, str_avdd_gpio); if (retval < 0) { LOGE("Fail to configure avdd GPIO %d\n", pwr->avdd_gpio); goto err_set_gpio_avdd; } } return 0; err_set_gpio_avdd: if (pwr->vdd_gpio >= 0) syna_spi_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL); err_set_gpio_vdd: if (rst->reset_gpio >= 0) syna_spi_request_gpio(rst->reset_gpio, false, 0, 0, NULL); err_set_gpio_reset: if (attn->irq_gpio >= 0) syna_spi_request_gpio(attn->irq_gpio, false, 0, 0, NULL); err_set_gpio_irq: return retval; } /** * syna_spi_enable_regulator() * * Enable or disable the regulator * * @param * [ in] hw_if: the handle of hw interface * [ in] en: '1' for enabling, and '0' for disabling * * @return * on success, 0; otherwise, negative value on error. */ static int syna_spi_enable_regulator(struct syna_hw_interface *hw_if, bool en) { int retval; struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; struct regulator *vdd_reg = pwr->vdd_reg_dev; struct regulator *avdd_reg = pwr->avdd_reg_dev; if (!en) { retval = 0; goto disable_pwr_reg; } if (vdd_reg) { retval = regulator_enable(vdd_reg); if (retval < 0) { LOGE("Fail to enable vdd regulator\n"); goto exit; } } if (avdd_reg) { retval = regulator_enable(avdd_reg); if (retval < 0) { LOGE("Fail to enable avdd regulator\n"); goto disable_avdd_reg; } syna_pal_sleep_ms(pwr->power_on_delay_ms); } return 0; disable_pwr_reg: if (vdd_reg) regulator_disable(vdd_reg); disable_avdd_reg: if (avdd_reg) regulator_disable(avdd_reg); exit: return retval; } /** * syna_spi_get_regulator() * * Acquire or release the regulator * * @param * [ in] hw_if: the handle of hw interface * [ in] get: '1' for getting the regulator, and '0' for removing * * @return * on success, 0; otherwise, negative value on error. */ static int syna_spi_get_regulator(struct syna_hw_interface *hw_if, bool get) { int retval; struct device *dev = syna_spi_device->dev.parent; struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; if (!get) { retval = 0; goto regulator_put; } if (pwr->vdd_reg_name != NULL && *pwr->vdd_reg_name != 0) { #ifdef DEV_MANAGED_API pwr->vdd_reg_dev = devm_regulator_get(dev, pwr->vdd_reg_name); #else /* Legacy API */ pwr->vdd_reg_dev = regulator_get(dev, pwr->vdd_reg_name); #endif if (IS_ERR((struct regulator *)pwr->vdd_reg_dev)) { LOGW("Vdd regulator is not ready\n"); retval = PTR_ERR((struct regulator *)pwr->vdd_reg_dev); goto exit; } } if (pwr->avdd_reg_name != NULL && *pwr->avdd_reg_name != 0) { #ifdef DEV_MANAGED_API pwr->avdd_reg_dev = devm_regulator_get(dev, pwr->avdd_reg_name); #else /* Legacy API */ pwr->avdd_reg_dev = regulator_get(dev, pwr->avdd_reg_name); #endif if (IS_ERR((struct regulator *)pwr->avdd_reg_dev)) { LOGW("AVdd regulator is not ready\n"); retval = PTR_ERR((struct regulator *)pwr->avdd_reg_dev); goto regulator_vdd_put; } } return 0; regulator_put: if (pwr->vdd_reg_dev) { #ifdef DEV_MANAGED_API devm_regulator_put(pwr->vdd_reg_dev); #else /* Legacy API */ regulator_put(pwr->vdd_reg_dev); #endif pwr->vdd_reg_dev = NULL; } regulator_vdd_put: if (pwr->avdd_reg_dev) { #ifdef DEV_MANAGED_API devm_regulator_put(pwr->avdd_reg_dev); #else /* Legacy API */ regulator_put(pwr->avdd_reg_dev); #endif pwr->avdd_reg_dev = NULL; } exit: return retval; } /** * syna_spi_enable_irq() * * Enable or disable the handling of interrupt * * @param * [ in] hw_if: the handle of hw interface * [ in] en: '1' for enabling, and '0' for disabling * * @return * 0 on success; otherwise, on error. */ static int syna_spi_enable_irq(struct syna_hw_interface *hw_if, bool en) { int retval = 0; struct syna_hw_attn_data *attn = &hw_if->bdata_attn; if (attn->irq_id == 0) return 0; syna_pal_mutex_lock(&attn->irq_en_mutex); /* enable the handling of interrupt */ if (en) { if (attn->irq_enabled) { LOGI("Interrupt already enabled\n"); retval = 0; goto exit; } enable_irq(attn->irq_id); attn->irq_enabled = true; LOGD("irq enabled\n"); } /* disable the handling of interrupt */ else { if (!attn->irq_enabled) { LOGI("Interrupt already disabled\n"); retval = 0; goto exit; } disable_irq_nosync(attn->irq_id); attn->irq_enabled = false; LOGD("irq disabled\n"); } exit: syna_pal_mutex_unlock(&attn->irq_en_mutex); return retval; } /** * syna_spi_parse_dt() * * Parse and obtain board specific data from the device tree source file. * Keep the data in structure syna_tcm_hw_data for later using. * * @param * [ in] hw_if: the handle of hw interface * [ in] dev: device model * * @return * on success, 0; otherwise, negative value on error. */ #ifdef CONFIG_OF static int syna_spi_parse_dt(struct syna_hw_interface *hw_if, struct device *dev) { int retval; int index; u32 value; u32 coords[2]; struct property *prop; struct device_node *np = dev->of_node; const char *name; struct syna_hw_attn_data *attn = &hw_if->bdata_attn; struct syna_hw_pwr_data *pwr = &hw_if->bdata_pwr; struct syna_hw_rst_data *rst = &hw_if->bdata_rst; struct syna_hw_bus_data *bus = &hw_if->bdata_io; struct of_phandle_args panelmap; struct drm_panel *panel = NULL; #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) u8 offload_id[4]; #endif if (of_property_read_bool(np, "synaptics,panel_map")) { for (index = 0 ;; index++) { retval = of_parse_phandle_with_fixed_args(np, "synaptics,panel_map", 0, index, &panelmap); if (retval) return -EPROBE_DEFER; panel = of_drm_find_panel(panelmap.np); of_node_put(panelmap.np); if (!IS_ERR_OR_NULL(panel)) { retval = of_property_read_string_index(np, "synaptics,firmware_names", index, &name); if (retval < 0) hw_if->fw_name = FW_IMAGE_NAME; else hw_if->fw_name = name; LOGI("Firmware name %s", hw_if->fw_name); break; } } } else { hw_if->fw_name = FW_IMAGE_NAME; } prop = of_find_property(np, "synaptics,irq-gpio", NULL); if (prop && prop->length) { attn->irq_gpio = of_get_named_gpio_flags(np, "synaptics,irq-gpio", 0, (enum of_gpio_flags *)&attn->irq_flags); } else { attn->irq_gpio = -1; } retval = of_property_read_u32(np, "synaptics,irq-on-state", &value); if (retval < 0) attn->irq_on_state = 0; else attn->irq_on_state = value; retval = of_property_read_string(np, "synaptics,avdd-name", &name); if (retval < 0) pwr->avdd_reg_name = NULL; else pwr->avdd_reg_name = name; retval = of_property_read_string(np, "synaptics,vdd-name", &name); if (retval < 0) pwr->vdd_reg_name = NULL; else pwr->vdd_reg_name = name; prop = of_find_property(np, "synaptics,vdd-gpio", NULL); if (prop && prop->length) { pwr->vdd_gpio = of_get_named_gpio_flags(np, "synaptics,vdd-gpio", 0, NULL); } else { pwr->vdd_gpio = -1; } prop = of_find_property(np, "synaptics,avdd-gpio", NULL); if (prop && prop->length) { pwr->avdd_gpio = of_get_named_gpio_flags(np, "synaptics,avdd-gpio", 0, NULL); } else { pwr->avdd_gpio = -1; } prop = of_find_property(np, "synaptics,power-on-state", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,power-on-state", &value); if (retval < 0) { LOGE("Fail to read power-on-state property\n"); return retval; } pwr->power_on_state = value; } else { pwr->power_on_state = 0; } prop = of_find_property(np, "synaptics,power-delay-ms", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,power-delay-ms", &value); if (retval < 0) { LOGE("Fail to read power-delay-ms property\n"); return retval; } pwr->power_on_delay_ms = value; } else { pwr->power_on_delay_ms = 0; } prop = of_find_property(np, "synaptics,reset-gpio", NULL); if (prop && prop->length) { rst->reset_gpio = of_get_named_gpio_flags(np, "synaptics,reset-gpio", 0, NULL); } else { rst->reset_gpio = -1; } prop = of_find_property(np, "synaptics,reset-on-state", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,reset-on-state", &value); if (retval < 0) { LOGE("Fail to read reset-on-state property\n"); return retval; } rst->reset_on_state = value; } else { rst->reset_on_state = 0; } prop = of_find_property(np, "synaptics,reset-active-ms", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,reset-active-ms", &value); if (retval < 0) { LOGE("Fail to read reset-active-ms property\n"); return retval; } rst->reset_active_ms = value; } else { rst->reset_active_ms = 0; } prop = of_find_property(np, "synaptics,reset-delay-ms", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,reset-delay-ms", &value); if (retval < 0) { LOGE("Fail to read reset-delay-ms property\n"); return retval; } rst->reset_delay_ms = value; } else { rst->reset_delay_ms = 0; } prop = of_find_property(np, "synaptics,spi-byte-delay-us", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,spi-byte-delay-us", &value); if (retval < 0) { LOGE("Fail to read byte-delay-us property\n"); return retval; } bus->spi_byte_delay_us = value; } else { bus->spi_byte_delay_us = 0; } prop = of_find_property(np, "synaptics,spi-block-delay-us", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,spi-block-delay-us", &value); if (retval < 0) { LOGE("Fail to read block-delay-us property\n"); return retval; } bus->spi_block_delay_us = value; } else { bus->spi_block_delay_us = 0; } prop = of_find_property(np, "synaptics,spi-mode", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,spi-mode", &value); if (retval < 0) { LOGE("Fail to read synaptics,spi-mode property\n"); return retval; } bus->spi_mode = value; } else { bus->spi_mode = 0; } prop = of_find_property(np, "synaptics,pixels-per-mm", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,pixels-per-mm", &value); if (retval < 0) { LOGE("Fail to read synaptics,pixels-per-mm\n"); return retval; } hw_if->pixels_per_mm = value; } else { /* * Set default as 1 to let the driver report the value from the * touch IC if pixels_per_mm is not set. */ hw_if->pixels_per_mm = 1; } prop = of_find_property(np, "synaptics,compression-threshold", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,compression-threshold", &value); if (retval < 0) { LOGE("Fail to read synaptics,compression-threshold\n"); return retval; } hw_if->compression_threhsold = value; } else { /* * Set default as 15. */ hw_if->compression_threhsold = 15; } prop = of_find_property(np, "synaptics,grip-delta-threshold", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,grip-delta-threshold", &value); if (retval < 0) { LOGE("Fail to read synaptics,grip-delta-threshold\n"); return retval; } hw_if->grip_delta_threshold = value; } else { /* * Set default as 50. */ hw_if->grip_delta_threshold = 50; } prop = of_find_property(np, "synaptics,grip-border-threshold", NULL); if (prop && prop->length) { retval = of_property_read_u32(np, "synaptics,grip-border-threshold", &value); if (retval < 0) { LOGE("Fail to read synaptics,grip-border-threshold\n"); return retval; } hw_if->grip_border_threshold = value; } else { /* * Set default as 50. */ hw_if->grip_border_threshold = 50; } hw_if->dynamic_report_rate = of_property_read_bool(np,"synaptics,dynamic-report-rate"); #if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) hw_if->offload_id = 0; retval = of_property_read_u8_array(np, "synaptics,touch_offload_id", offload_id, 4); if (retval == -EINVAL) dev_err(dev, "Failed to read synaptics,touch_offload_id with error = %d\n", retval); else { hw_if->offload_id = *(u32 *)offload_id; dev_info(dev, "Offload device ID = \"%c%c%c%c\" / 0x%08X\n", offload_id[0], offload_id[1], offload_id[2], offload_id[3], hw_if->offload_id); } #endif if (of_property_read_u32_array(np, "synaptics,udfps-coords", coords, 2)) { dev_err(dev, "synaptics,udfps-coords not found\n"); coords[0] = 0; coords[1] = 0; } hw_if->udfps_x = coords[0]; hw_if->udfps_y = coords[1]; return 0; } #endif /** * syna_tcm_spi_alloc_mem() * * Manage and allocate the memory to buf being as a temporary buffer for IO * * @param * [ in] count: number of spi_transfer structures to send * [ in] size: size of temporary buffer * * @return * on success, 0; otherwise, negative value on error. */ static int syna_spi_alloc_mem(unsigned int count, unsigned int size) { static unsigned int xfer_count; if (count > xfer_count) { syna_pal_mem_free((void *)xfer); xfer = syna_pal_mem_alloc(count, sizeof(*xfer)); if (!xfer) { LOGE("Fail to allocate memory for xfer\n"); xfer_count = 0; return -ENOMEM; } xfer_count = count; } else { syna_pal_mem_set(xfer, 0, count * sizeof(*xfer)); } if (size > buf_size) { if (buf_size) syna_pal_mem_free((void *)buf); buf = syna_pal_mem_alloc(size, sizeof(unsigned char)); if (!buf) { LOGE("Fail to allocate memory for buf\n"); buf_size = 0; return -ENOMEM; } buf_size = size; } return 0; } /** * syna_spi_read() * * TouchCom over SPI requires the host to assert the SSB signal to address * the device and retrieve the data. * * @param * [ in] hw_if: the handle of hw interface * [out] rd_data: buffer for storing data retrieved from device * [ in] rd_len: number of bytes retrieved from device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_spi_read(struct syna_hw_interface *hw_if, unsigned char *rd_data, unsigned int rd_len) { int retval; unsigned int idx; struct spi_message msg; struct spi_device *spi = hw_if->pdev; struct syna_hw_bus_data *bus = &hw_if->bdata_io; if (!spi) { LOGE("Invalid bus io device\n"); return -EINVAL; } syna_pal_mutex_lock(&bus->io_mutex); spi_message_init(&msg); if (bus->spi_byte_delay_us == 0) retval = syna_spi_alloc_mem(1, rd_len); else retval = syna_spi_alloc_mem(rd_len, 1); if (retval < 0) { LOGE("Fail to allocate memory\n"); goto exit; } if (bus->spi_byte_delay_us == 0) { syna_pal_mem_set(buf, 0xff, rd_len); xfer[0].len = rd_len; xfer[0].tx_buf = buf; xfer[0].rx_buf = rd_data; if (bus->spi_block_delay_us) xfer[0].delay_usecs = bus->spi_block_delay_us; spi_message_add_tail(&xfer[0], &msg); } else { buf[0] = 0xff; for (idx = 0; idx < rd_len; idx++) { xfer[idx].len = 1; xfer[idx].tx_buf = buf; xfer[idx].rx_buf = &rd_data[idx]; xfer[idx].delay_usecs = bus->spi_byte_delay_us; if (bus->spi_block_delay_us && (idx == rd_len - 1)) xfer[idx].delay_usecs = bus->spi_block_delay_us; spi_message_add_tail(&xfer[idx], &msg); } } retval = spi_sync(spi, &msg); if (retval != 0) { LOGE("Failed to complete SPI transfer, error = %d\n", retval); goto exit; } retval = rd_len; exit: syna_pal_mutex_unlock(&bus->io_mutex); return retval; } /** * syna_spi_write() * * TouchCom over SPI requires the host to assert the SSB signal to address * the device and send the data to the device. * * @param * [ in] hw_if: the handle of hw interface * [ in] wr_data: written data * [ in] wr_len: length of written data in bytes * * @return * on success, 0; otherwise, negative value on error. */ static int syna_spi_write(struct syna_hw_interface *hw_if, unsigned char *wr_data, unsigned int wr_len) { int retval; unsigned int idx; struct spi_message msg; struct spi_device *spi = hw_if->pdev; struct syna_hw_bus_data *bus = &hw_if->bdata_io; if (!spi) { LOGE("Invalid bus io device\n"); return -EINVAL; } syna_pal_mutex_lock(&bus->io_mutex); spi_message_init(&msg); if (bus->spi_byte_delay_us == 0) retval = syna_spi_alloc_mem(1, 0); else retval = syna_spi_alloc_mem(wr_len, 0); if (retval < 0) { LOGE("Failed to allocate memory\n"); goto exit; } if (bus->spi_byte_delay_us == 0) { xfer[0].len = wr_len; xfer[0].tx_buf = wr_data; if (bus->spi_block_delay_us) xfer[0].delay_usecs = bus->spi_block_delay_us; spi_message_add_tail(&xfer[0], &msg); } else { for (idx = 0; idx < wr_len; idx++) { xfer[idx].len = 1; xfer[idx].tx_buf = &wr_data[idx]; xfer[idx].delay_usecs = bus->spi_byte_delay_us; if (bus->spi_block_delay_us && (idx == wr_len - 1)) xfer[idx].delay_usecs = bus->spi_block_delay_us; spi_message_add_tail(&xfer[idx], &msg); } } retval = spi_sync(spi, &msg); if (retval != 0) { LOGE("Fail to complete SPI transfer, error = %d\n", retval); goto exit; } retval = wr_len; exit: syna_pal_mutex_unlock(&bus->io_mutex); return retval; } /** * syna_hw_interface * * Provide the hardware specific settings in defaults. * Be noted the followings could be changed after .dtsi is parsed */ static struct syna_hw_interface syna_spi_hw_if = { .bdata_io = { .type = BUS_TYPE_SPI, .rd_chunk_size = RD_CHUNK_SIZE, .wr_chunk_size = WR_CHUNK_SIZE, }, .bdata_attn = { .irq_enabled = false, .irq_on_state = 0, }, .bdata_rst = { .reset_on_state = 0, .reset_delay_ms = 200, .reset_active_ms = 20, }, .bdata_pwr = { .power_on_state = 1, .power_on_delay_ms = 200, }, .ops_power_on = syna_spi_enable_regulator, .ops_hw_reset = syna_spi_hw_reset, .ops_read_data = syna_spi_read, .ops_write_data = syna_spi_write, .ops_enable_irq = syna_spi_enable_irq, }; /** * syna_spi_probe() * * Prepare the specific hardware interface and register the platform spi device * * @param * [ in] spi: spi device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_spi_probe(struct spi_device *spi) { int retval; struct syna_hw_attn_data *attn = &syna_spi_hw_if.bdata_attn; struct syna_hw_bus_data *bus = &syna_spi_hw_if.bdata_io; if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { LOGE("Full duplex not supported by host\n"); return -EIO; } /* allocate an spi platform device */ syna_spi_device = platform_device_alloc(PLATFORM_DRIVER_NAME, 0); if (!syna_spi_device) { LOGE("Fail to allocate platform device\n"); return _ENODEV; } #ifdef CONFIG_OF syna_spi_parse_dt(&syna_spi_hw_if, &spi->dev); #endif syna_pal_mutex_alloc(&attn->irq_en_mutex); syna_pal_mutex_alloc(&bus->io_mutex); switch (bus->spi_mode) { case 0: spi->mode = SPI_MODE_0; break; case 1: spi->mode = SPI_MODE_1; break; case 2: spi->mode = SPI_MODE_2; break; case 3: spi->mode = SPI_MODE_3; break; } /* keep the i/o device */ syna_spi_hw_if.pdev = spi; syna_spi_device->dev.parent = &spi->dev; syna_spi_device->dev.platform_data = &syna_spi_hw_if; spi->bits_per_word = 8; spi->rt = true; /* set up spi driver */ retval = spi_setup(spi); if (retval < 0) { LOGE("Fail to set up SPI protocol driver\n"); return retval; } /* enable the regulators */ retval = syna_spi_get_regulator(&syna_spi_hw_if, true); if (retval < 0) return retval; /* initialize the gpio pins */ retval = syna_spi_config_gpio(&syna_spi_hw_if); if (retval < 0) { LOGE("Fail to config gpio\n"); return retval; } /* register the spi platform device */ retval = platform_device_add(syna_spi_device); if (retval < 0) { LOGE("Fail to add platform device\n"); return retval; } return 0; } /** * syna_spi_remove() * * Unregister the platform spi device * * @param * [ in] spi: spi device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_spi_remove(struct spi_device *spi) { struct syna_hw_attn_data *attn = &syna_spi_hw_if.bdata_attn; struct syna_hw_pwr_data *pwr = &syna_spi_hw_if.bdata_pwr; struct syna_hw_rst_data *rst = &syna_spi_hw_if.bdata_rst; struct syna_hw_bus_data *bus = &syna_spi_hw_if.bdata_io; /* disable gpios */ if (pwr->avdd_gpio >= 0) syna_spi_request_gpio(pwr->avdd_gpio, false, 0, 0, NULL); if (pwr->vdd_gpio >= 0) syna_spi_request_gpio(pwr->vdd_gpio, false, 0, 0, NULL); if (rst->reset_gpio >= 0) syna_spi_request_gpio(rst->reset_gpio, false, 0, 0, NULL); if (attn->irq_gpio >= 0) syna_spi_request_gpio(attn->irq_gpio, false, 0, 0, NULL); /* disable the regulators */ syna_spi_get_regulator(&syna_spi_hw_if, false); syna_pal_mutex_free(&attn->irq_en_mutex); syna_pal_mutex_free(&bus->io_mutex); /* remove the platform device */ syna_spi_device->dev.platform_data = NULL; platform_device_unregister(syna_spi_device); return 0; } /** * Describe an spi device driver and its related declarations */ static const struct spi_device_id syna_spi_id_table[] = { {SPI_MODULE_NAME, 0}, {}, }; MODULE_DEVICE_TABLE(spi, syna_spi_id_table); #ifdef CONFIG_OF static const struct of_device_id syna_spi_of_match_table[] = { { .compatible = "synaptics,tcm-spi", }, {}, }; MODULE_DEVICE_TABLE(of, syna_spi_of_match_table); #else #define syna_spi_of_match_table NULL #endif static struct spi_driver syna_spi_driver = { .driver = { .name = SPI_MODULE_NAME, .owner = THIS_MODULE, .of_match_table = syna_spi_of_match_table, }, .probe = syna_spi_probe, .remove = syna_spi_remove, .id_table = syna_spi_id_table, }; /** * syna_hw_interface_init() * * Initialize the lower-level hardware interface module. * After returning, the handle of hw interface should be ready. * * @param * void * * @return * on success, 0; otherwise, negative value on error. */ int syna_hw_interface_init(void) { return spi_register_driver(&syna_spi_driver); } /** * syna_hw_interface_exit() * * Delete the lower-level hardware interface module * * @param * void * * @return * none. */ void syna_hw_interface_exit(void) { syna_pal_mem_free((void *)buf); syna_pal_mem_free((void *)xfer); spi_unregister_driver(&syna_spi_driver); } MODULE_AUTHOR("Synaptics, Inc."); MODULE_DESCRIPTION("Synaptics TouchCom SPI Bus Module"); MODULE_LICENSE("GPL v2");