| // SPDX-License-Identifier: GPL-2.0 |
| /* Himax Android Driver Sample Code for QCT platform |
| * |
| * Copyright (C) 2019 Himax Corporation. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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 "himax_platform.h" |
| #include "himax_common.h" |
| |
| int i2c_error_count; |
| bool ic_boot_done; |
| struct spi_device *spi; |
| |
| static uint8_t *g_w_xfer_data; |
| static uint8_t *g_r_xfer_data; |
| |
| int himax_dev_set(struct himax_ts_data *ts) |
| { |
| int ret = 0; |
| |
| ts->input_dev = input_allocate_device(); |
| |
| if (ts->input_dev == NULL) { |
| ret = -ENOMEM; |
| E("%s: Failed to allocate input device\n", __func__); |
| return ret; |
| } |
| |
| ts->input_dev->name = "himax-touchscreen"; |
| |
| if (!ic_data->HX_STYLUS_FUNC) |
| goto skip_stylus_operation; |
| |
| ts->stylus_dev = input_allocate_device(); |
| |
| if (ts->stylus_dev == NULL) { |
| ret = -ENOMEM; |
| E("%s: Failed to allocate input device-stylus_dev\n", __func__); |
| input_free_device(ts->input_dev); |
| return ret; |
| } |
| |
| ts->stylus_dev->name = "himax-stylus"; |
| skip_stylus_operation: |
| |
| return ret; |
| } |
| int himax_input_register_device(struct input_dev *input_dev) |
| { |
| return input_register_device(input_dev); |
| } |
| |
| void himax_vk_parser(struct device_node *dt, struct himax_platform_data *pdata) |
| { |
| u32 data = 0; |
| uint8_t cnt = 0, i = 0; |
| uint32_t coords[4] = {0}; |
| struct device_node *node, *pp = NULL; |
| struct himax_virtual_key *vk; |
| |
| node = of_parse_phandle(dt, "virtualkey", 0); |
| |
| if (node == NULL) { |
| I(" DT-No vk info in DT\n"); |
| return; |
| } |
| while ((pp = of_get_next_child(node, pp))) |
| cnt++; |
| |
| if (!cnt) |
| return; |
| |
| vk = kcalloc(cnt, sizeof(struct himax_virtual_key), GFP_KERNEL); |
| if (vk == NULL) { |
| E("%s, vk init fail!\n", __func__); |
| return; |
| } |
| pp = NULL; |
| |
| while ((pp = of_get_next_child(node, pp))) { |
| if (of_property_read_u32(pp, "idx", &data) == 0) |
| vk[i].index = data; |
| |
| if (of_property_read_u32_array(pp, "range", coords, 4) == 0) { |
| vk[i].x_range_min = coords[0]; |
| vk[i].x_range_max = coords[1]; |
| vk[i].y_range_min = coords[2]; |
| vk[i].y_range_max = coords[3]; |
| } else { |
| I(" range faile\n"); |
| } |
| |
| i++; |
| } |
| |
| pdata->virtual_key = vk; |
| |
| for (i = 0; i < cnt; i++) |
| I(" vk[%d] idx:%d x_min:%d, y_max:%d\n", i, |
| pdata->virtual_key[i].index, |
| pdata->virtual_key[i].x_range_min, |
| pdata->virtual_key[i].y_range_max); |
| |
| } |
| |
| #if defined(HX_CONFIG_DRM_PANEL) |
| struct drm_panel *active_panel; |
| |
| int himax_ts_check_dt(struct device_node *np) |
| { |
| int i = 0; |
| int count = 0; |
| struct device_node *node = NULL; |
| struct drm_panel *panel = NULL; |
| |
| I("%s start\n", __func__); |
| count = of_count_phandle_with_args(np, "panel", NULL); |
| if (count <= 0) { |
| I("%s count= %d\n", __func__, count); |
| return 0; |
| } |
| |
| for (i = 0; i < count; i++) { |
| node = of_parse_phandle(np, "panel", i); |
| panel = of_drm_find_panel(node); |
| of_node_put(node); |
| if (!IS_ERR(panel)) { |
| I("%s find drm_panel successfully\n", __func__); |
| active_panel = panel; |
| I("active_panel=%s\n", active_panel); |
| return 0; |
| } |
| } |
| E("%s find drm_panel failed\n", __func__); |
| return PTR_ERR(panel); |
| } |
| #endif |
| |
| static int himax_check_panel_map(struct device_node *np, |
| struct himax_platform_data *pdata) |
| { |
| int ret = 0; |
| int index; |
| struct of_phandle_args panelmap; |
| struct drm_panel *panel = NULL; |
| |
| if (of_property_read_bool(np, "himax,panel_map")) { |
| for (index = 0 ;; index++) { |
| ret = of_parse_phandle_with_fixed_args(np, |
| "himax,panel_map", |
| 1, |
| index, |
| &panelmap); |
| if (ret) { |
| E("%s: Can't find display panel!\n", __func__); |
| return -EPROBE_DEFER; |
| } |
| panel = of_drm_find_panel(panelmap.np); |
| of_node_put(panelmap.np); |
| if (!IS_ERR_OR_NULL(panel)) { |
| pdata->panel = panel; |
| pdata->initial_panel_index = panelmap.args[0]; |
| break; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| int himax_parse_dt(struct himax_ts_data *ts, struct himax_platform_data *pdata) |
| { |
| int coords_size = 0; |
| uint32_t coords[4] = {0}; |
| struct property *prop; |
| struct device_node *dt = ts->dev->of_node; |
| u32 data = 0; |
| int ret = 0; |
| #if defined(HX_CONFIG_DRM) |
| #if defined(HX_CONFIG_DRM_PANEL) |
| ret = himax_ts_check_dt(dt); |
| if (ret == -EPROBE_DEFER) |
| E("himax_ts_check_dt failed\n"); |
| #endif |
| #endif |
| |
| /* Check if panel(s) exist or not. */ |
| ret = himax_check_panel_map(dt, pdata); |
| if (ret) |
| return ret; |
| |
| prop = of_find_property(dt, "himax,panel-coords", NULL); |
| if (prop) { |
| coords_size = prop->length / sizeof(u32); |
| if (coords_size != 4) |
| D(" %s:Invalid panel coords size %d\n", |
| __func__, coords_size); |
| } |
| |
| ret = of_property_read_u32_array(dt, "himax,panel-coords", |
| coords, coords_size); |
| if (ret == 0) { |
| pdata->abs_x_min = coords[0]; |
| pdata->abs_x_max = (coords[1] - 1); |
| pdata->abs_y_min = coords[2]; |
| pdata->abs_y_max = (coords[3] - 1); |
| I(" DT-%s:panel-coords = %d, %d, %d, %d\n", __func__, |
| pdata->abs_x_min, |
| pdata->abs_x_max, |
| pdata->abs_y_min, |
| pdata->abs_y_max); |
| } |
| |
| prop = of_find_property(dt, "himax,display-coords", NULL); |
| if (prop) { |
| coords_size = prop->length / sizeof(u32); |
| if (coords_size != 4) |
| D(" %s:Invalid display coords size %d\n", |
| __func__, coords_size); |
| } |
| |
| ret = of_property_read_u32_array(dt, "himax,display-coords", |
| coords, coords_size); |
| if (ret && (ret != -EINVAL)) { |
| D(" %s:Fail to read display-coords %d\n", __func__, ret); |
| return ret; |
| } |
| pdata->screenWidth = coords[1]; |
| pdata->screenHeight = coords[3]; |
| I(" DT-%s:display-coords = (%d, %d)\n", __func__, |
| pdata->screenWidth, |
| pdata->screenHeight); |
| |
| pdata->gpio_irq = of_get_named_gpio(dt, "himax,irq-gpio", 0); |
| if (!gpio_is_valid(pdata->gpio_irq)) |
| I(" DT:gpio_irq value is not valid\n"); |
| |
| pdata->gpio_reset = of_get_named_gpio(dt, "himax,rst-gpio", 0); |
| if (!gpio_is_valid(pdata->gpio_reset)) |
| I(" DT:gpio_rst value is not valid\n"); |
| |
| pdata->gpio_3v3_en = of_get_named_gpio(dt, "himax,3v3-gpio", 0); |
| if (!gpio_is_valid(pdata->gpio_3v3_en)) |
| I(" DT:gpio_3v3_en value is not valid\n"); |
| |
| I(" DT:gpio_irq=%d, gpio_rst=%d, gpio_3v3_en=%d\n", |
| pdata->gpio_irq, |
| pdata->gpio_reset, |
| pdata->gpio_3v3_en); |
| |
| #if defined(HX_PON_PIN_SUPPORT) |
| pdata->gpio_pon = of_get_named_gpio(dt, "himax,pon-gpio", 0); |
| |
| if (!gpio_is_valid(pdata->gpio_pon)) |
| I(" DT:gpio_pon value is not valid\n"); |
| |
| pdata->lcm_rst = of_get_named_gpio(dt, "himax,lcm-rst", 0); |
| |
| if (!gpio_is_valid(pdata->lcm_rst)) |
| I(" DT:tp-rst value is not valid\n"); |
| |
| I(" DT:pdata->gpio_pon=%d, pdata->lcm_rst=%d\n", |
| pdata->gpio_pon, pdata->lcm_rst); |
| #endif |
| |
| if (of_property_read_u32(dt, "report_type", &data) == 0) { |
| pdata->protocol_type = data; |
| I(" DT:protocol_type=%d\n", pdata->protocol_type); |
| } |
| himax_vk_parser(dt, pdata); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(himax_parse_dt); |
| |
| #if defined(HX_PARSE_FROM_DT) |
| static void hx_generate_ic_info_from_dt( |
| uint32_t proj_id, char *buff, char *main_str, char *item_str) |
| { |
| if (proj_id == 0xffff) |
| snprintf(buff, 128, "%s,%s", main_str, item_str); |
| else |
| snprintf(buff, 128, "%s_%04X,%s", main_str, proj_id, item_str); |
| |
| } |
| static void hx_generate_fw_name_from_dt( |
| uint32_t proj_id, char *buff, char *main_str, char *item_str) |
| { |
| if (proj_id == 0xffff) |
| snprintf(buff, 128, "%s_%s", main_str, item_str); |
| else |
| snprintf(buff, 128, "%s_%04X_%s", main_str, proj_id, item_str); |
| |
| } |
| |
| void himax_parse_dt_ic_info(struct himax_ts_data *ts, |
| struct himax_platform_data *pdata) |
| { |
| struct device_node *dt = ts->dev->of_node; |
| u32 data = 0; |
| char *str_rx_num = "rx-num"; |
| char *str_tx_num = "tx-num"; |
| char *str_bt_num = "bt-num"; |
| char *str_max_pt = "max-pt"; |
| char *str_int_edge = "int-edge"; |
| char *str_stylus_func = "stylus-func"; |
| char *str_firmware_name_tail = "firmware.bin"; |
| char *str_mp_firmware_name_tail = "mpfw.bin"; |
| char buff[128] = {0}; |
| |
| hx_generate_ic_info_from_dt(g_proj_id, buff, ts->chip_name, str_rx_num); |
| if (of_property_read_u32(dt, buff, &data) == 0) { |
| ic_data->HX_RX_NUM = data; |
| I("%s,Now parse:%s=%d\n", __func__, buff, ic_data->HX_RX_NUM); |
| } else |
| I("%s, No definition: %s!\n", __func__, buff); |
| |
| hx_generate_ic_info_from_dt(g_proj_id, buff, ts->chip_name, str_tx_num); |
| if (of_property_read_u32(dt, buff, &data) == 0) { |
| ic_data->HX_TX_NUM = data; |
| I("%s,Now parse:%s=%d\n", __func__, buff, ic_data->HX_TX_NUM); |
| } else |
| I("%s, No definition: %s!\n", __func__, buff); |
| |
| hx_generate_ic_info_from_dt(g_proj_id, buff, ts->chip_name, str_bt_num); |
| if (of_property_read_u32(dt, buff, &data) == 0) { |
| ic_data->HX_BT_NUM = data; |
| I("%s,Now parse:%s=%d\n", __func__, buff, ic_data->HX_BT_NUM); |
| } else |
| I("%s, No definition: %s!\n", __func__, buff); |
| |
| hx_generate_ic_info_from_dt(g_proj_id, buff, ts->chip_name, str_max_pt); |
| if (of_property_read_u32(dt, buff, &data) == 0) { |
| ic_data->HX_MAX_PT = data; |
| I("%s,Now parse:%s=%d\n", __func__, buff, ic_data->HX_MAX_PT); |
| } else |
| I("%s, No definition: %s!\n", __func__, buff); |
| |
| hx_generate_ic_info_from_dt( |
| g_proj_id, buff, ts->chip_name, str_int_edge); |
| if (of_property_read_u32(dt, buff, &data) == 0) { |
| ic_data->HX_INT_IS_EDGE = data; |
| I("%s,Now parse:%s=%d\n", |
| __func__, buff, ic_data->HX_INT_IS_EDGE); |
| } else |
| I("%s, No definition: %s!\n", __func__, buff); |
| |
| hx_generate_ic_info_from_dt(g_proj_id, buff, |
| ts->chip_name, str_stylus_func); |
| if (of_property_read_u32(dt, buff, &data) == 0) { |
| ic_data->HX_STYLUS_FUNC = data; |
| I("%s,Now parse:%s=%d\n", |
| __func__, buff, ic_data->HX_STYLUS_FUNC); |
| } else |
| I("%s, No definition: %s!\n", __func__, buff); |
| |
| hx_generate_fw_name_from_dt(g_proj_id, buff, ts->chip_name, |
| str_firmware_name_tail); |
| I("%s,buff=%s!\n", __func__, buff); |
| #if defined(HX_BOOT_UPGRADE) |
| g_fw_boot_upgrade_name = kzalloc(sizeof(buff), GFP_KERNEL); |
| memcpy(&g_fw_boot_upgrade_name[0], &buff[0], sizeof(buff)); |
| I("%s,g_fw_boot_upgrade_name=%s!\n", |
| __func__, g_fw_boot_upgrade_name); |
| #endif |
| |
| hx_generate_fw_name_from_dt(g_proj_id, buff, ts->chip_name, |
| str_mp_firmware_name_tail); |
| I("%s,buff=%s!\n", __func__, buff); |
| #if defined(HX_ZERO_FLASH) |
| g_fw_mp_upgrade_name = kzalloc(sizeof(buff), GFP_KERNEL); |
| memcpy(&g_fw_mp_upgrade_name[0], &buff[0], sizeof(buff)); |
| I("%s,g_fw_mp_upgrade_name=%s!\n", __func__, g_fw_mp_upgrade_name); |
| #endif |
| |
| I(" DT:rx, tx, bt, pt, int, stylus\n"); |
| I(" DT:%d, %d, %d, %d, %d, %d\n", ic_data->HX_RX_NUM, |
| ic_data->HX_TX_NUM, ic_data->HX_BT_NUM, ic_data->HX_MAX_PT, |
| ic_data->HX_INT_IS_EDGE, ic_data->HX_STYLUS_FUNC); |
| } |
| EXPORT_SYMBOL(himax_parse_dt_ic_info); |
| #endif |
| |
| static int himax_spi_read(uint8_t *command, uint8_t command_len, uint8_t *data, |
| uint32_t length) |
| { |
| struct spi_message m; |
| int result = NO_ERR; |
| int retry; |
| int error; |
| struct spi_transfer t = { |
| .len = command_len + length, |
| }; |
| |
| memset(g_w_xfer_data, 0, command_len + length); |
| memcpy(g_w_xfer_data, command, command_len); |
| |
| t.tx_buf = g_w_xfer_data; |
| t.rx_buf = g_r_xfer_data; |
| |
| if (length > HX_MAX_READ_SZ) { |
| E("%s, data length is over than MAX SIZE!\n", __func__); |
| result = -EIO; |
| goto END; |
| } |
| |
| spi_message_init(&m); |
| spi_message_add_tail(&t, &m); |
| |
| |
| for (retry = 0; retry < HIMAX_BUS_RETRY_TIMES; retry++) { |
| error = spi_sync(private_ts->spi, &m); |
| if (unlikely(error)) |
| E("SPI read error: %d\n", error); |
| else |
| break; |
| } |
| |
| if (retry == HIMAX_BUS_RETRY_TIMES) { |
| E("%s: SPI read error retry over %d\n", |
| __func__, HIMAX_BUS_RETRY_TIMES); |
| result = -EIO; |
| goto END; |
| } else { |
| memcpy(&data[0], &g_r_xfer_data[command_len], |
| sizeof(uint8_t) * length); |
| } |
| |
| END: |
| return result; |
| } |
| |
| static int himax_spi_write(uint8_t *buf, uint32_t length) |
| { |
| int status; |
| struct spi_message m; |
| struct spi_transfer t = { |
| .tx_buf = buf, |
| .len = length, |
| }; |
| |
| spi_message_init(&m); |
| spi_message_add_tail(&t, &m); |
| |
| status = spi_sync(private_ts->spi, &m); |
| |
| if (status == 0) { |
| status = m.status; |
| if (status == 0) |
| status = m.actual_length; |
| } |
| return status; |
| |
| } |
| |
| int himax_bus_read(uint8_t command, uint8_t *data, uint32_t length) |
| { |
| int result = 0; |
| uint8_t *spi_format_buf = g_r_xfer_data; |
| |
| mutex_lock(&(private_ts->rw_lock)); |
| spi_format_buf[0] = 0xF3; |
| spi_format_buf[1] = command; |
| spi_format_buf[2] = 0x00; |
| |
| result = himax_spi_read(&spi_format_buf[0], 3, data, length); |
| mutex_unlock(&(private_ts->rw_lock)); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(himax_bus_read); |
| |
| int himax_bus_write(uint8_t command, uint8_t *data, uint32_t length) |
| { |
| uint8_t *spi_format_buf = g_w_xfer_data; |
| int result = 0; |
| |
| mutex_lock(&(private_ts->rw_lock)); |
| spi_format_buf[0] = 0xF2; |
| spi_format_buf[1] = command; |
| |
| memcpy(spi_format_buf + 2, data, length); |
| |
| result = himax_spi_write(spi_format_buf, length + 2); |
| mutex_unlock(&(private_ts->rw_lock)); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(himax_bus_write); |
| |
| void himax_int_enable(int enable) |
| { |
| struct himax_ts_data *ts = private_ts; |
| unsigned long irqflags = 0; |
| int irqnum = ts->hx_irq; |
| |
| spin_lock_irqsave(&ts->irq_lock, irqflags); |
| I("%s: Entering! irqnum = %d\n", __func__, irqnum); |
| if (enable == 1 && atomic_read(&ts->irq_state) == 0) { |
| atomic_set(&ts->irq_state, 1); |
| enable_irq(irqnum); |
| private_ts->irq_enabled = 1; |
| } else if (enable == 0 && atomic_read(&ts->irq_state) == 1) { |
| atomic_set(&ts->irq_state, 0); |
| disable_irq_nosync(irqnum); |
| private_ts->irq_enabled = 0; |
| } |
| |
| I("enable = %d\n", enable); |
| spin_unlock_irqrestore(&ts->irq_lock, irqflags); |
| } |
| EXPORT_SYMBOL(himax_int_enable); |
| |
| #if defined(HX_RST_PIN_FUNC) |
| void himax_rst_gpio_set(int pinnum, uint8_t value) |
| { |
| gpio_direction_output(pinnum, value); |
| } |
| EXPORT_SYMBOL(himax_rst_gpio_set); |
| #endif |
| |
| uint8_t himax_int_gpio_read(int pinnum) |
| { |
| return gpio_get_value(pinnum); |
| } |
| |
| #if defined(CONFIG_HMX_DB) |
| static int himax_regulator_configure(struct himax_platform_data *pdata) |
| { |
| int retval; |
| /* struct i2c_client *client = private_ts->client; */ |
| |
| pdata->vcc_dig = regulator_get(private_ts->dev, "vdd"); |
| |
| if (IS_ERR(pdata->vcc_dig)) { |
| E("%s: Failed to get regulator vdd\n", |
| __func__); |
| retval = PTR_ERR(pdata->vcc_dig); |
| return retval; |
| } |
| |
| pdata->vcc_ana = regulator_get(private_ts->dev, "avdd"); |
| |
| if (IS_ERR(pdata->vcc_ana)) { |
| E("%s: Failed to get regulator avdd\n", |
| __func__); |
| retval = PTR_ERR(pdata->vcc_ana); |
| regulator_put(pdata->vcc_dig); |
| return retval; |
| } |
| |
| return 0; |
| }; |
| |
| static void himax_regulator_deinit(struct himax_platform_data *pdata) |
| { |
| I("%s: entered.\n", __func__); |
| |
| if (!IS_ERR(pdata->vcc_ana)) |
| regulator_put(pdata->vcc_ana); |
| |
| if (!IS_ERR(pdata->vcc_dig)) |
| regulator_put(pdata->vcc_dig); |
| |
| I("%s: regulator put, completed.\n", __func__); |
| }; |
| |
| static int himax_power_on(struct himax_platform_data *pdata, bool on) |
| { |
| int retval; |
| |
| if (on) { |
| retval = regulator_enable(pdata->vcc_dig); |
| |
| if (retval) { |
| E("%s: Failed to enable regulator vdd\n", |
| __func__); |
| return retval; |
| } |
| |
| /*msleep(100);*/ |
| usleep_range(1000, 1001); |
| retval = regulator_enable(pdata->vcc_ana); |
| |
| if (retval) { |
| E("%s: Failed to enable regulator avdd\n", |
| __func__); |
| regulator_disable(pdata->vcc_dig); |
| return retval; |
| } |
| } else { |
| regulator_disable(pdata->vcc_dig); |
| regulator_disable(pdata->vcc_ana); |
| } |
| |
| return 0; |
| } |
| |
| int himax_gpio_power_config(struct himax_platform_data *pdata) |
| { |
| int error; |
| /* struct i2c_client *client = private_ts->client; */ |
| |
| error = himax_regulator_configure(pdata); |
| |
| if (error) { |
| E("Failed to intialize hardware\n"); |
| goto err_regulator_not_on; |
| } |
| |
| #if defined(HX_RST_PIN_FUNC) |
| |
| if (gpio_is_valid(pdata->gpio_reset)) { |
| /* configure touchscreen reset out gpio */ |
| error = gpio_request(pdata->gpio_reset, "hmx_reset_gpio"); |
| |
| if (error) { |
| E("unable to request gpio [%d]\n", pdata->gpio_reset); |
| goto err_gpio_reset_req; |
| } |
| |
| error = gpio_direction_output(pdata->gpio_reset, 0); |
| |
| if (error) { |
| E("unable to set direction for gpio [%d]\n", |
| pdata->gpio_reset); |
| goto err_gpio_reset_dir; |
| } |
| } |
| |
| #endif |
| #if defined(HX_PON_PIN_SUPPORT) |
| if (pdata->lcm_rst >= 0) { |
| error = gpio_direction_output(pdata->lcm_rst, 0); |
| if (error) { |
| E("unable to set direction for lcm_rst [%d]\n", |
| pdata->lcm_rst); |
| } |
| } |
| #endif |
| error = himax_power_on(pdata, true); |
| |
| if (error) { |
| E("Failed to power on hardware\n"); |
| goto err_power_on; |
| } |
| |
| if (gpio_is_valid(pdata->gpio_irq)) { |
| /* configure touchscreen irq gpio */ |
| error = gpio_request(pdata->gpio_irq, "hmx_gpio_irq"); |
| |
| if (error) { |
| E("unable to request gpio [%d]\n", |
| pdata->gpio_irq); |
| goto err_req_irq_gpio; |
| } |
| |
| error = gpio_direction_input(pdata->gpio_irq); |
| |
| if (error) { |
| E("unable to set direction for gpio [%d]\n", |
| pdata->gpio_irq); |
| goto err_set_gpio_irq; |
| } |
| |
| private_ts->hx_irq = gpio_to_irq(pdata->gpio_irq); |
| } else { |
| E("irq gpio not provided\n"); |
| goto err_req_irq_gpio; |
| } |
| #if defined(HX_PON_PIN_SUPPORT) |
| if (pdata->lcm_rst >= 0) { |
| error = gpio_direction_output(pdata->lcm_rst, 1); |
| |
| if (error) { |
| E("lcm_rst unable to set direction for gpio [%d]\n", |
| pdata->lcm_rst); |
| } |
| } |
| #endif |
| /*msleep(20);*/ |
| usleep_range(2000, 2001); |
| #if defined(HX_RST_PIN_FUNC) |
| |
| if (gpio_is_valid(pdata->gpio_reset)) { |
| error = gpio_direction_output(pdata->gpio_reset, 1); |
| |
| if (error) { |
| E("unable to set direction for gpio [%d]\n", |
| pdata->gpio_reset); |
| goto err_gpio_reset_set_high; |
| } |
| } |
| |
| #endif |
| return 0; |
| #if defined(HX_RST_PIN_FUNC) |
| err_gpio_reset_set_high: |
| #endif |
| err_set_gpio_irq: |
| if (gpio_is_valid(pdata->gpio_irq)) |
| gpio_free(pdata->gpio_irq); |
| |
| err_req_irq_gpio: |
| himax_power_on(pdata, false); |
| err_power_on: |
| #if defined(HX_RST_PIN_FUNC) |
| err_gpio_reset_dir: |
| if (gpio_is_valid(pdata->gpio_reset)) |
| gpio_free(pdata->gpio_reset); |
| |
| err_gpio_reset_req: |
| #endif |
| himax_regulator_deinit(pdata); |
| err_regulator_not_on: |
| return error; |
| } |
| |
| #else //#if defined(CONFIG_HMX_DB) |
| int himax_gpio_power_config(struct himax_platform_data *pdata) |
| { |
| int error = 0; |
| #if defined(HX_RST_PIN_FUNC) |
| if (pdata->gpio_reset >= 0) { |
| error = gpio_request_one(pdata->gpio_reset, GPIOF_OUT_INIT_HIGH, "himax-reset"); |
| |
| if (error < 0) { |
| E("%s: request reset pin failed\n", __func__); |
| goto err_gpio_reset_req; |
| } |
| |
| error = gpio_direction_output(pdata->gpio_reset, 0); |
| |
| if (error) { |
| E("unable to set direction for gpio [%d]\n", |
| pdata->gpio_reset); |
| goto err_gpio_reset_dir; |
| } |
| } |
| #endif |
| |
| #if defined(HX_PON_PIN_SUPPORT) |
| if (pdata->lcm_rst >= 0) { |
| error = gpio_direction_output(pdata->lcm_rst, 0); |
| if (error) { |
| E("unable to set direction for lcm_rst [%d]\n", |
| pdata->lcm_rst); |
| } |
| } |
| |
| if (gpio_is_valid(pdata->gpio_pon)) { |
| error = gpio_request(pdata->gpio_pon, "hmx_pon_gpio"); |
| |
| if (error) { |
| E("unable to request pon gpio [%d]\n", pdata->gpio_pon); |
| goto err_gpio_pon_req; |
| } |
| |
| error = gpio_direction_output(pdata->gpio_pon, 0); |
| |
| I("gpio_pon LOW [%d]\n", pdata->gpio_pon); |
| |
| if (error) { |
| E("unable to set direction for pon gpio [%d]\n", |
| pdata->gpio_pon); |
| goto err_gpio_pon_dir; |
| } |
| } |
| #endif |
| |
| |
| if (pdata->gpio_3v3_en >= 0) { |
| error = gpio_request(pdata->gpio_3v3_en, "himax-3v3_en"); |
| |
| if (error < 0) { |
| E("%s: request 3v3_en pin failed\n", __func__); |
| goto err_gpio_3v3_req; |
| } |
| |
| gpio_direction_output(pdata->gpio_3v3_en, 1); |
| I("3v3_en set 1 get pin = %d\n", |
| gpio_get_value(pdata->gpio_3v3_en)); |
| } |
| |
| if (gpio_is_valid(pdata->gpio_irq)) { |
| /* configure touchscreen irq gpio */ |
| error = gpio_request_one(pdata->gpio_irq, GPIOF_DIR_IN, "himax-irq"); |
| |
| if (error) { |
| E("unable to request gpio [%d]\n", pdata->gpio_irq); |
| goto err_gpio_irq_req; |
| } |
| |
| error = gpio_direction_input(pdata->gpio_irq); |
| |
| if (error) { |
| E("unable to set direction for gpio [%d]\n", |
| pdata->gpio_irq); |
| goto err_gpio_irq_set_input; |
| } |
| |
| private_ts->hx_irq = gpio_to_irq(pdata->gpio_irq); |
| } else { |
| E("irq gpio not provided\n"); |
| goto err_gpio_irq_req; |
| } |
| |
| usleep_range(2000, 2001); |
| |
| #if defined(HX_PON_PIN_SUPPORT) |
| msleep(20); |
| |
| if (pdata->lcm_rst >= 0) { |
| error = gpio_direction_output(pdata->lcm_rst, 1); |
| |
| if (error) { |
| E("lcm_rst unable to set direction for gpio [%d]\n", |
| pdata->lcm_rst); |
| } |
| } |
| msleep(20); |
| #endif |
| |
| #if defined(HX_RST_PIN_FUNC) |
| |
| if (pdata->gpio_reset >= 0) { |
| error = gpio_direction_output(pdata->gpio_reset, 1); |
| |
| if (error) { |
| E("unable to set direction for gpio [%d]\n", |
| pdata->gpio_reset); |
| goto err_gpio_reset_set_high; |
| } |
| } |
| #endif |
| |
| #if defined(HX_PON_PIN_SUPPORT) |
| msleep(800); |
| |
| if (gpio_is_valid(pdata->gpio_pon)) { |
| |
| error = gpio_direction_output(pdata->gpio_pon, 1); |
| |
| I("gpio_pon HIGH [%d]\n", pdata->gpio_pon); |
| |
| if (error) { |
| E("gpio_pon unable to set direction for gpio [%d]\n", |
| pdata->gpio_pon); |
| goto err_gpio_pon_set_high; |
| } |
| } |
| #endif |
| return error; |
| |
| #if defined(HX_PON_PIN_SUPPORT) |
| err_gpio_pon_set_high: |
| #endif |
| #if defined(HX_RST_PIN_FUNC) |
| err_gpio_reset_set_high: |
| #endif |
| err_gpio_irq_set_input: |
| if (gpio_is_valid(pdata->gpio_irq)) |
| gpio_free(pdata->gpio_irq); |
| err_gpio_irq_req: |
| if (pdata->gpio_3v3_en >= 0) |
| gpio_free(pdata->gpio_3v3_en); |
| err_gpio_3v3_req: |
| #if defined(HX_PON_PIN_SUPPORT) |
| err_gpio_pon_dir: |
| if (gpio_is_valid(pdata->gpio_pon)) |
| gpio_free(pdata->gpio_pon); |
| err_gpio_pon_req: |
| #endif |
| #if defined(HX_RST_PIN_FUNC) |
| err_gpio_reset_dir: |
| if (pdata->gpio_reset >= 0) |
| gpio_free(pdata->gpio_reset); |
| err_gpio_reset_req: |
| #endif |
| return error; |
| } |
| |
| #endif |
| |
| void himax_gpio_power_deconfig(struct himax_platform_data *pdata) |
| { |
| if (gpio_is_valid(pdata->gpio_irq)) |
| gpio_free(pdata->gpio_irq); |
| |
| #if defined(HX_RST_PIN_FUNC) |
| if (gpio_is_valid(pdata->gpio_reset)) |
| gpio_free(pdata->gpio_reset); |
| #endif |
| |
| #if defined(CONFIG_HMX_DB) |
| himax_power_on(pdata, false); |
| himax_regulator_deinit(pdata); |
| #else |
| if (pdata->gpio_3v3_en >= 0) |
| gpio_free(pdata->gpio_3v3_en); |
| |
| #if defined(HX_PON_PIN_SUPPORT) |
| if (gpio_is_valid(pdata->gpio_pon)) |
| gpio_free(pdata->gpio_pon); |
| #endif |
| |
| #endif |
| } |
| |
| static void himax_ts_isr_func(struct himax_ts_data *ts) |
| { |
| himax_ts_work(ts); |
| } |
| |
| irqreturn_t himax_ts_thread(int irq, void *ptr) |
| { |
| struct himax_ts_data *ts = (struct himax_ts_data *)ptr; |
| if (himax_ts_set_bus_ref(ts, HIMAX_TS_BUS_REF_IRQ, true) < 0) { |
| /* Interrupt during bus suspend */ |
| I("%s: Skipping stray interrupt since bus is suspended(power_status: %d)\n", |
| __func__, ts->power_status); |
| return IRQ_HANDLED; |
| } |
| |
| himax_ts_isr_func((struct himax_ts_data *)ptr); |
| |
| himax_ts_set_bus_ref(ts, HIMAX_TS_BUS_REF_IRQ, false); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void himax_ts_work_func(struct work_struct *work) |
| { |
| struct himax_ts_data *ts = container_of(work, |
| struct himax_ts_data, work); |
| |
| himax_ts_work(ts); |
| } |
| |
| int himax_int_register_trigger(void) |
| { |
| int ret = 0; |
| struct himax_ts_data *ts = private_ts; |
| |
| if (ic_data->HX_INT_IS_EDGE) { |
| I("%s edge triiger falling\n", __func__); |
| ret = request_threaded_irq(ts->hx_irq, NULL, himax_ts_thread, |
| IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
| HIMAX_common_NAME, ts); |
| } else { |
| I("%s level trigger low\n", __func__); |
| ret = request_threaded_irq(ts->hx_irq, NULL, himax_ts_thread, |
| IRQF_TRIGGER_LOW | IRQF_ONESHOT, HIMAX_common_NAME, ts); |
| } |
| |
| return ret; |
| } |
| |
| int himax_int_en_set(void) |
| { |
| int ret = NO_ERR; |
| |
| ret = himax_int_register_trigger(); |
| return ret; |
| } |
| |
| int himax_ts_register_interrupt(void) |
| { |
| struct himax_ts_data *ts = private_ts; |
| /* struct i2c_client *client = private_ts->client; */ |
| int ret = 0; |
| |
| ts->irq_enabled = 0; |
| |
| /* Work functon */ |
| if (private_ts->hx_irq) {/*INT mode*/ |
| ts->use_irq = 1; |
| ret = himax_int_register_trigger(); |
| |
| if (ret == 0) { |
| ts->irq_enabled = 1; |
| atomic_set(&ts->irq_state, 1); |
| I("%s: irq enabled at gpio: %d\n", __func__, |
| private_ts->hx_irq); |
| #if defined(HX_SMART_WAKEUP) |
| irq_set_irq_wake(private_ts->hx_irq, 1); |
| #endif |
| } else { |
| ts->use_irq = 0; |
| E("%s: request_irq failed\n", __func__); |
| } |
| } else { |
| I("%s: private_ts->hx_irq is empty, use polling mode.\n", |
| __func__); |
| } |
| |
| /*if use polling mode need to disable HX_ESD_RECOVERY function*/ |
| if (!ts->use_irq) { |
| ts->himax_wq = create_singlethread_workqueue("himax_touch"); |
| INIT_WORK(&ts->work, himax_ts_work_func); |
| hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); |
| ts->timer.function = himax_ts_timer_func; |
| hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); |
| I("%s: polling mode enabled\n", __func__); |
| } |
| |
| return ret; |
| } |
| |
| int himax_ts_unregister_interrupt(void) |
| { |
| struct himax_ts_data *ts = private_ts; |
| int ret = 0; |
| |
| I("%s: entered.\n", __func__); |
| |
| /* Work functon */ |
| if (private_ts->hx_irq && ts->use_irq) {/*INT mode*/ |
| #if defined(HX_SMART_WAKEUP) |
| irq_set_irq_wake(ts->hx_irq, 0); |
| #endif |
| free_irq(ts->hx_irq, ts); |
| I("%s: irq disabled at qpio: %d\n", __func__, |
| private_ts->hx_irq); |
| } |
| |
| /*if use polling mode need to disable HX_ESD_RECOVERY function*/ |
| if (!ts->use_irq) { |
| hrtimer_cancel(&ts->timer); |
| cancel_work_sync(&ts->work); |
| if (ts->himax_wq != NULL) |
| destroy_workqueue(ts->himax_wq); |
| I("%s: polling mode destroyed", __func__); |
| } |
| |
| return ret; |
| } |
| |
| static int himax_common_suspend(struct device *dev) |
| { |
| struct himax_ts_data *ts = dev_get_drvdata(dev); |
| |
| I("%s: enter\n", __func__); |
| #if defined(HX_CONFIG_DRM) && !defined(HX_CONFIG_FB) |
| if (!ts->initialized) |
| return -ECANCELED; |
| #endif |
| himax_chip_common_suspend(ts); |
| return 0; |
| } |
| #if !defined(HX_CONTAINER_SPEED_UP) |
| static int himax_common_resume(struct device *dev) |
| { |
| struct himax_ts_data *ts = dev_get_drvdata(dev); |
| |
| I("%s: enter\n", __func__); |
| #if defined(HX_CONFIG_DRM) && !defined(HX_CONFIG_FB) |
| /* |
| * wait until device resume for TDDI |
| * TDDI: Touch and display Driver IC |
| */ |
| if (!ts->initialized) { |
| if (himax_chip_common_init()) |
| return -ECANCELED; |
| } |
| #endif |
| himax_chip_common_resume(ts); |
| return 0; |
| } |
| #endif |
| |
| #if defined(HX_CONFIG_FB) |
| int fb_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct fb_event *evdata = data; |
| int *blank; |
| struct himax_ts_data *ts = |
| container_of(self, struct himax_ts_data, fb_notif); |
| |
| I(" %s\n", __func__); |
| |
| if (ic_boot_done != 1) { |
| E("%s: IC is booting\n", __func__); |
| return -ECANCELED; |
| } |
| |
| if (evdata && evdata->data && |
| #if defined(HX_CONTAINER_SPEED_UP) |
| event == FB_EARLY_EVENT_BLANK |
| #else |
| event == FB_EVENT_BLANK |
| #endif |
| && ts != NULL && |
| ts->dev != NULL) { |
| blank = evdata->data; |
| |
| switch (*blank) { |
| case FB_BLANK_UNBLANK: |
| #if defined(HX_CONTAINER_SPEED_UP) |
| queue_delayed_work(ts->ts_int_workqueue, |
| &ts->ts_int_work, |
| msecs_to_jiffies(DELAY_TIME)); |
| #else |
| himax_common_resume(ts->dev); |
| #endif |
| break; |
| |
| case FB_BLANK_POWERDOWN: |
| case FB_BLANK_HSYNC_SUSPEND: |
| case FB_BLANK_VSYNC_SUSPEND: |
| case FB_BLANK_NORMAL: |
| himax_common_suspend(ts->dev); |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| #elif defined(HX_CONFIG_DRM_PANEL) |
| int drm_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct drm_panel_notifier *evdata = data; |
| int *blank; |
| struct himax_ts_data *ts = |
| container_of(self, struct himax_ts_data, fb_notif); |
| |
| if (!evdata) |
| return 0; |
| |
| D("DRM %s\n", __func__); |
| |
| if (ic_boot_done != 1) { |
| E("%s: IC is booting\n", __func__); |
| return -ECANCELED; |
| } |
| |
| if (evdata->data |
| && event == DRM_PANEL_EARLY_EVENT_BLANK |
| && ts |
| && ts->client) { |
| blank = evdata->data; |
| switch (*blank) { |
| case DRM_PANEL_BLANK_POWERDOWN: |
| if (!ts->initialized) |
| return -ECANCELED; |
| himax_common_suspend(&ts->client->dev); |
| break; |
| } |
| } |
| |
| if (evdata->data |
| && event == DRM_PANEL_EVENT_BLANK |
| && ts |
| && ts->client) { |
| blank = evdata->data; |
| switch (*blank) { |
| case DRM_PANEL_BLANK_UNBLANK: |
| himax_common_resume(&ts->client->dev); |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| #elif defined(HX_CONFIG_DRM) |
| int drm_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct msm_drm_notifier *evdata = data; |
| int *blank; |
| struct himax_ts_data *ts = |
| container_of(self, struct himax_ts_data, fb_notif); |
| |
| if (!evdata || (evdata->id != 0)) |
| return 0; |
| |
| D("DRM %s\n", __func__); |
| |
| if (ic_boot_done != 1) { |
| E("%s: IC is booting\n", __func__); |
| return -ECANCELED; |
| } |
| |
| if (evdata->data |
| && event == MSM_DRM_EARLY_EVENT_BLANK |
| && ts != NULL |
| && ts->dev != NULL) { |
| blank = evdata->data; |
| switch (*blank) { |
| case MSM_DRM_BLANK_POWERDOWN: |
| if (!ts->initialized) |
| return -ECANCELED; |
| himax_common_suspend(ts->dev); |
| break; |
| } |
| } |
| |
| if (evdata->data |
| && event == MSM_DRM_EVENT_BLANK |
| && ts != NULL |
| && ts->dev != NULL) { |
| blank = evdata->data; |
| switch (*blank) { |
| case MSM_DRM_BLANK_UNBLANK: |
| himax_common_resume(ts->dev); |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| int himax_chip_common_probe(struct spi_device *spi) |
| { |
| struct himax_ts_data *ts; |
| int ret = 0; |
| |
| I("Enter %s\n", __func__); |
| if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { |
| dev_err(&spi->dev, |
| "%s: Full duplex not supported by host\n", |
| __func__); |
| return -EIO; |
| } |
| |
| /* command[2] + address[4] + data[] */ |
| g_w_xfer_data = kzalloc(sizeof(uint8_t) * (HX_MAX_WRITE_SZ+6), |
| GFP_KERNEL); |
| if (g_w_xfer_data == NULL) { |
| E("%s: allocate g_w_xfer_data failed\n", __func__); |
| ret = -ENOMEM; |
| goto err_alloc_g_w_xfer_data_failed; |
| } |
| |
| g_r_xfer_data = kzalloc(sizeof(uint8_t) * HX_MAX_READ_SZ, |
| GFP_KERNEL); |
| if (g_r_xfer_data == NULL) { |
| E("%s, allocate g_r_xfer_data fail!\n", __func__); |
| ret = -ENOMEM; |
| goto err_alloc_g_r_xfer_data_failed; |
| } |
| |
| ts = kzalloc(sizeof(struct himax_ts_data), GFP_KERNEL); |
| if (ts == NULL) { |
| E("%s: allocate himax_ts_data failed\n", __func__); |
| ret = -ENOMEM; |
| goto err_alloc_data_failed; |
| } |
| |
| private_ts = ts; |
| spi->bits_per_word = 8; |
| spi->mode = SPI_MODE_3; |
| spi->chip_select = 0; |
| |
| ts->spi = spi; |
| mutex_init(&ts->rw_lock); |
| ts->dev = &spi->dev; |
| dev_set_drvdata(&spi->dev, ts); |
| spi_set_drvdata(spi, ts); |
| |
| ts->initialized = false; |
| ret = himax_chip_common_init(); |
| if (ret < 0) |
| goto err_common_init_failed; |
| |
| return ret; |
| |
| err_common_init_failed: |
| kfree(ts); |
| err_alloc_data_failed: |
| kfree(g_r_xfer_data); |
| g_r_xfer_data = NULL; |
| err_alloc_g_r_xfer_data_failed: |
| kfree(g_w_xfer_data); |
| g_w_xfer_data = NULL; |
| err_alloc_g_w_xfer_data_failed: |
| |
| return ret; |
| } |
| |
| int himax_chip_common_remove(struct spi_device *spi) |
| { |
| struct himax_ts_data *ts = spi_get_drvdata(spi); |
| |
| if (ts->initialized) |
| himax_chip_common_deinit(); |
| |
| ts->spi = NULL; |
| /* spin_unlock_irq(&ts->spi_lock); */ |
| spi_set_drvdata(spi, NULL); |
| kfree(g_w_xfer_data); |
| kfree(g_r_xfer_data); |
| |
| I("%s: completed.\n", __func__); |
| |
| return 0; |
| } |
| |
| static const struct dev_pm_ops himax_common_pm_ops = { |
| #if (!defined(HX_CONFIG_FB)) && (!defined(HX_CONFIG_DRM)) |
| .suspend = himax_common_suspend, |
| .resume = himax_common_resume, |
| #endif |
| }; |
| |
| #if defined(CONFIG_OF) |
| static const struct of_device_id himax_match_table[] = { |
| {.compatible = "himax,hxcommon" }, |
| {}, |
| }; |
| #else |
| #define himax_match_table NULL |
| #endif |
| |
| static struct spi_driver himax_common_driver = { |
| .driver = { |
| .name = HIMAX_common_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = himax_match_table, |
| }, |
| .probe = himax_chip_common_probe, |
| .remove = himax_chip_common_remove, |
| }; |
| |
| static int __init himax_common_init(void) |
| { |
| I("Himax common touch panel driver init\n"); |
| D("Himax check double loading\n"); |
| if (g_mmi_refcnt++ > 0) { |
| I("Himax driver has been loaded! ignoring....\n"); |
| return 0; |
| } |
| spi_register_driver(&himax_common_driver); |
| |
| return 0; |
| } |
| |
| static void __exit himax_common_exit(void) |
| { |
| #if !defined(KERNEL_VER_ABOVE_5_10) |
| if (spi) { |
| spi_unregister_device(spi); |
| spi = NULL; |
| } |
| #endif |
| spi_unregister_driver(&himax_common_driver); |
| } |
| |
| late_initcall(himax_common_init); |
| module_exit(himax_common_exit); |
| |
| MODULE_DESCRIPTION("Himax_common driver"); |
| MODULE_LICENSE("GPL"); |