| /* |
| * Copyright (c) 2014 Intel Corporation. All Rights Reserved. |
| * |
| * Partially based on m-5mols kernel driver, |
| * Copyright (C) 2011 Samsung Electronics Co., Ltd. |
| * |
| * Partially based on jc_v4l2 kernel driver from http://opensource.samsung.com |
| * Copyright (c) 2011, Code Aurora Forum. 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 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/atomisp_platform.h> |
| #include <linux/delay.h> |
| #include <linux/firmware.h> |
| #include <linux/i2c.h> |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/spi/spi.h> |
| #include <media/v4l2-device.h> |
| #include <linux/sizes.h> |
| #include "m10mo.h" |
| |
| |
| /* |
| * Currently the FW image and dump paths are hardcoded here. |
| * TBD: flexible interface for defining proper path as needed |
| */ |
| #define M10MO_FW_LOG1_NAME "/data/M10MO_log1" |
| #define M10MO_FW_LOG2_1_NAME "/data/M10MO_log2_1" |
| #define M10MO_FW_LOG2_2_NAME "/data/M10MO_log2_2" |
| #define M10MO_FW_LOG2_3_NAME "/data/M10MO_log2_3" |
| #define M10MO_FW_LOG3_NAME "/data/M10MO_log3" |
| |
| #define M10MO_FW_LOG_SUFFIX ".bin" |
| #define M10MO_FW_LOG_MAX_NAME_LEN (128) |
| |
| #define M10MO_FW_DUMP_PATH "/data/M10MO_dump.bin" |
| #define M10MO_FW_NAME "M10MO_fw.bin" |
| |
| #define SRAM_BUFFER_ADDRESS 0x01100000 |
| #define SDRAM_BUFFER_ADDRESS 0x20000000 |
| |
| #define M10MO_FLASH_READ_BASE_ADDR 0x18000000 |
| #define PLL_SETTINGS_24MHZ 0x00170141 |
| #define PLL_SETTINGS_19_2MHZ 0x001d0152 |
| |
| #define PORT_SETTINGS0_ADDR 0x90001200 |
| #define PORT_SETTINGS1_ADDR 0x90001000 |
| #define PORT_SETTINGS2_ADDR 0x90001100 |
| |
| #define PORT_SETTING_DELAY (10*1000) |
| #define I2C_DELAY (10*1000) |
| |
| #define I2C_DUMP_SIZE 0x20 /* keep as power of 2 values */ |
| #define FW_SIZE 0x00200000 |
| #define FLASH_BLOCK_SIZE 0x10000 |
| #define SIO_BLOCK_SIZE 8192 |
| #define DUMP_BLOCK_SIZE 0x1000 |
| |
| #define FW_VERSION_INFO_ADDR 0x181EF080 |
| |
| #define ONE_WRITE_SIZE 64 |
| |
| #define ONE_WAIT_LOOP_TIME 10 /* milliseconds */ |
| #define CHIP_ERASE_TIMEOUT (15000 / ONE_WAIT_LOOP_TIME) |
| #define SECTOR_ERASE_TIMEOUT (5000 / ONE_WAIT_LOOP_TIME) |
| #define PROGRAMMING_TIMEOUT (15000 / ONE_WAIT_LOOP_TIME) |
| #define CHECKSUM_TIMEOUT (5000 / ONE_WAIT_LOOP_TIME) |
| #define STATE_TRANSITION_TIMEOUT (3000 / ONE_WAIT_LOOP_TIME) |
| |
| /* Tables for m10mo pin configurations */ |
| static const u8 buf_port_settings0_m10mo[] = { |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, |
| 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, |
| }; |
| |
| static const u8 buf_port_settings1_m10mo[] = { |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| }; |
| |
| static const u8 buf_port_settings2_m10mo[] = { |
| 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x14, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| }; |
| |
| static const u32 m10mo_fw_address[] = { |
| M10MO_FW_VERSION_INFO_ADDR_0, |
| M10MO_FW_VERSION_INFO_ADDR_1, |
| }; |
| |
| static int m10mo_set_flash_address(struct v4l2_subdev *sd, u32 addr) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int ret; |
| ret = m10mo_writel(sd, CATEGORY_FLASHROM, REG_FLASH_ADD, addr); |
| if (ret) |
| dev_err(&client->dev, "Set flash address failed\n"); |
| return ret; |
| } |
| |
| static u32 m10mo_get_pll_cfg(u32 freq) |
| { |
| u32 ret; |
| switch(freq) { |
| case 24000000: |
| ret = PLL_SETTINGS_24MHZ; |
| break; |
| case 19200000: |
| ret = PLL_SETTINGS_19_2MHZ; |
| break; |
| default: |
| /* Defaults to development board xtal freq */ |
| ret = PLL_SETTINGS_24MHZ; |
| break; |
| } |
| return ret; |
| } |
| |
| static int m10mo_wait_operation_complete(struct v4l2_subdev *sd, u8 reg, |
| u32 timeout) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int res; |
| do { |
| msleep(ONE_WAIT_LOOP_TIME); |
| m10mo_readb(sd, CATEGORY_FLASHROM, reg, &res); |
| } while ((res != 0) && --timeout); |
| |
| if (!timeout) { |
| dev_err(&client->dev, |
| "timeout while waiting for chip op to finish\n"); |
| return -ETIME; |
| } |
| return 0; |
| } |
| |
| int m10mo_update_pll_setting(struct v4l2_subdev *sd) |
| { |
| struct m10mo_device *m10mo_dev = to_m10mo_sensor(sd); |
| int err; |
| |
| err = m10mo_writel(sd, CATEGORY_FLASHROM, |
| REG_PLL_VALUES, |
| m10mo_get_pll_cfg(m10mo_dev->ref_clock)); |
| |
| return err; |
| } |
| |
| static int m10mo_to_fw_access_mode(struct m10mo_device *m10mo_dev) |
| { |
| struct v4l2_subdev *sd = &m10mo_dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int err; |
| |
| err = m10mo_memory_write(sd, M10MO_MEMORY_WRITE_8BIT, SZ_64, |
| PORT_SETTINGS0_ADDR , (u8 *)buf_port_settings0_m10mo); |
| if (err) |
| goto fail; |
| |
| usleep_range(PORT_SETTING_DELAY, PORT_SETTING_DELAY); |
| |
| err = m10mo_memory_write(sd, M10MO_MEMORY_WRITE_8BIT, SZ_64, |
| PORT_SETTINGS1_ADDR, (u8 *)buf_port_settings1_m10mo); |
| if (err) |
| goto fail; |
| |
| usleep_range(PORT_SETTING_DELAY, PORT_SETTING_DELAY); |
| |
| err = m10mo_memory_write(sd, M10MO_MEMORY_WRITE_8BIT, SZ_64, |
| PORT_SETTINGS2_ADDR, (u8 *)buf_port_settings2_m10mo); |
| if (err) |
| goto fail; |
| usleep_range(PORT_SETTING_DELAY, PORT_SETTING_DELAY); |
| |
| err = m10mo_writel(sd, CATEGORY_FLASHROM, |
| REG_PLL_VALUES, |
| m10mo_get_pll_cfg(m10mo_dev->ref_clock)); |
| if (err) |
| goto fail; |
| return 0; |
| fail: |
| dev_err(&client->dev, "transition to fw mode failed\n"); |
| return err; |
| } |
| |
| |
| static int m10mo_memory_dump(struct m10mo_device *m10mo_dev, u16 len, |
| u32 addr, u8 *val) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(&m10mo_dev->sd); |
| struct i2c_msg msg; |
| unsigned char data[8]; |
| u16 len_received; |
| int i, err = 0; |
| |
| if (!client->adapter) |
| return -ENODEV; |
| |
| if (len >= (sizeof(m10mo_dev->message_buffer) - 3)) |
| return -EINVAL; |
| |
| msg.addr = client->addr; |
| msg.flags = 0; |
| msg.len = sizeof(data); |
| msg.buf = data; |
| |
| /* high byte goes out first */ |
| data[0] = 0x00; |
| data[1] = M10MO_MEMORY_READ_8BIT; |
| data[2] = (addr >> 24) & 0xFF; |
| data[3] = (addr >> 16) & 0xFF; |
| data[4] = (addr >> 8) & 0xFF; |
| data[5] = addr & 0xFF; |
| data[6] = (len >> 8) & 0xFF; |
| data[7] = len & 0xFF; |
| |
| for (i = M10MO_I2C_RETRY; i; i--) { |
| err = i2c_transfer(client->adapter, &msg, 1); |
| if (err == 1) |
| break; |
| usleep_range(I2C_DELAY, I2C_DELAY); |
| } |
| |
| if (err != 1) |
| return err; |
| |
| msg.flags = I2C_M_RD; |
| msg.len = len + 3; |
| msg.buf = m10mo_dev->message_buffer; |
| for (i = M10MO_I2C_RETRY; i; i--) { |
| err = i2c_transfer(client->adapter, &msg, 1); |
| if (err == 1) |
| break; |
| usleep_range(I2C_DELAY, I2C_DELAY); |
| } |
| |
| if (err != 1) |
| return err; |
| |
| len_received = m10mo_dev->message_buffer[1] << 8 | |
| m10mo_dev->message_buffer[2]; |
| if (len != len_received) |
| dev_err(&client->dev, |
| "expected length %d, but return length %d\n", |
| len, len_received); |
| |
| memcpy(val, m10mo_dev->message_buffer + 3, len); |
| return err; |
| } |
| |
| int m10mo_dump_fw(struct m10mo_device *m10mo_dev) |
| { |
| struct v4l2_subdev *sd = &m10mo_dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct file *fp; |
| mm_segment_t old_fs; |
| u8 *buf; |
| u32 addr, unit, count; |
| int i; |
| int err; |
| |
| dev_dbg(&client->dev, "Begin FW dump to file %s\n", M10MO_FW_DUMP_PATH); |
| |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| |
| fp = filp_open(M10MO_FW_DUMP_PATH, |
| O_WRONLY|O_CREAT|O_TRUNC, S_IRUGO|S_IWUGO|S_IXUSR); |
| if (IS_ERR(fp)) { |
| dev_err(&client->dev, |
| "failed to open %s, err %ld\n", |
| M10MO_FW_DUMP_PATH, PTR_ERR(fp)); |
| err = -ENOENT; |
| goto out_file; |
| } |
| |
| err = m10mo_to_fw_access_mode(m10mo_dev); |
| if (err) |
| goto out_close; |
| |
| buf = kmalloc(DUMP_BLOCK_SIZE, GFP_KERNEL); |
| if (!buf) { |
| dev_err(&client->dev, "Failed to allocate memory\n"); |
| err = -ENOMEM; |
| goto out_close; |
| } |
| |
| err = m10mo_writeb(sd, CATEGORY_FLASHROM, |
| REG_FW_READ, REG_FW_READ_CMD_READ); |
| |
| if (err) { |
| dev_err(&client->dev, "FW read cmd failed %d\n", err); |
| goto out_mem_free; |
| } |
| |
| addr = M10MO_FLASH_READ_BASE_ADDR; |
| unit = I2C_DUMP_SIZE; |
| count = FW_SIZE / I2C_DUMP_SIZE; |
| for (i = 0; i < count; i++) { |
| err = m10mo_memory_dump(m10mo_dev, |
| unit, |
| addr + (i * unit), |
| buf); |
| if (err < 0) { |
| dev_err(&client->dev, "Memory dump failed %d\n", err); |
| goto out_mem_free; |
| } |
| vfs_write(fp, buf, unit, &fp->f_pos); |
| } |
| dev_dbg(&client->dev, "End of FW dump to file\n"); |
| |
| out_mem_free: |
| kfree(buf); |
| out_close: |
| if (!IS_ERR(fp)) |
| filp_close(fp, current->files); |
| out_file: |
| set_fs(old_fs); |
| |
| return err; |
| } |
| |
| static void m10mo_gen_log_name(char *name, char *prefix) |
| { |
| static long long time; |
| |
| time = ktime_to_ms(ktime_get()); |
| snprintf(name, M10MO_FW_LOG_MAX_NAME_LEN, "%s_%lld%s", prefix, time, M10MO_FW_LOG_SUFFIX); |
| } |
| |
| int m10mo_dump_string_log3(struct v4l2_subdev *sd) |
| { |
| u32 addr; |
| mm_segment_t old_fs; |
| struct file *fp; |
| u32 len = MAX_LOG_STR_LEN; |
| u32 ret = 0; |
| u32 count = 0; |
| u32 count_len = 0; |
| u32 ptr = 0; |
| char *buf = NULL; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| char filename[M10MO_FW_LOG_MAX_NAME_LEN] = {0}; |
| |
| m10mo_gen_log_name(filename, M10MO_FW_LOG3_NAME); |
| |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| fp = filp_open(filename, |
| O_WRONLY|O_CREAT|O_TRUNC, S_IRUGO|S_IWUGO|S_IXUSR); |
| if (IS_ERR(fp)) { |
| dev_err(&client->dev, |
| "failed to open %s, err %ld\n", |
| M10MO_FW_DUMP_PATH, PTR_ERR(fp)); |
| ret = -ENOENT; |
| goto out_file; |
| } |
| |
| buf = kmalloc(DUMP_BLOCK_SIZE, GFP_KERNEL); |
| if (!buf) { |
| dev_err(&client->dev, "Failed to allocate memory\n"); |
| ret = -ENOMEM; |
| goto out_close; |
| } |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_MODE, LOG_TRACE_MODE); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_DISABLE); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ADD_SHOW, LOG_ADD_SHOW_INIT_VALUE); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| while (count++ < MAX_MEM_DUMP_NUM_LOG3) { |
| ret = m10mo_writew(sd, CATEGORY_LOGLEDFLASH, LOG_SEL1, ptr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_OUTPUT_STR); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| do { |
| ret = m10mo_readb(sd, CATEGORY_LOGLEDFLASH, LOG_STR_LEN, &len); |
| if (ret < 0) |
| goto out_mem_free; |
| msleep(10); |
| count_len++; |
| } while ((len == MAX_LOG_STR_LEN) && (count_len < 10)); |
| |
| if (len == MIN_LOG_STR_LEN) { |
| goto out_mem_free; |
| } else { |
| ret = m10mo_readl(sd, CATEGORY_LOGLEDFLASH, LOG_STR_ADD3, &addr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_memory_read(sd, len, addr, buf); |
| if (ret < 0) |
| goto out_mem_free; |
| /* Do not add buf[len] = '\n'; */ |
| vfs_write(fp, buf, len, &fp->f_pos); |
| } |
| len = MAX_LOG_STR_LEN; |
| ptr = ptr + 1; |
| } |
| |
| out_mem_free: |
| kfree(buf); |
| out_close: |
| if (!IS_ERR(fp)) |
| filp_close(fp, current->files); |
| out_file: |
| set_fs(old_fs); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s, dump log error\n", __func__); |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_DISABLE); |
| if (ret < 0) |
| dev_err(&client->dev, "%s, m10mo_writeb error\n", __func__); |
| |
| return ret; |
| } |
| |
| /* Not verified */ |
| int m10mo_dump_string_log2_3(struct v4l2_subdev *sd) |
| { |
| u32 addr, i; |
| mm_segment_t old_fs; |
| struct file *fp; |
| u32 len = MAX_LOG_STR_LEN_LOG2; |
| u32 ret = 0; |
| u32 count = 0; |
| u32 unit_count = 0; |
| u32 ptr = 0; |
| char *buf = NULL; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| char filename[M10MO_FW_LOG_MAX_NAME_LEN] = {0}; |
| |
| m10mo_gen_log_name(filename, M10MO_FW_LOG2_3_NAME); |
| |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| fp = filp_open(filename, |
| O_WRONLY|O_CREAT|O_TRUNC, S_IRUGO|S_IWUGO|S_IXUSR); |
| if (IS_ERR(fp)) { |
| dev_err(&client->dev, |
| "failed to open %s, err %ld\n", |
| M10MO_FW_DUMP_PATH, PTR_ERR(fp)); |
| ret = -ENOENT; |
| goto out_file; |
| } |
| |
| buf = kmalloc(DUMP_BLOCK_SIZE, GFP_KERNEL); |
| if (!buf) { |
| dev_err(&client->dev, "Failed to allocate memory\n"); |
| ret = -ENOMEM; |
| goto out_close; |
| } |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_MODE, LOG_ANALYZE_MODE2); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| while (count++ < MAX_MEM_DUMP_NUM) { |
| ret = m10mo_writew(sd, CATEGORY_LOGLEDFLASH, LOG_SEL1, ptr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_OUTPUT_STR); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| do { |
| ret = m10mo_readw(sd, CATEGORY_LOGLEDFLASH, LOG_DATA_LEN1, &len); |
| if (ret < 0) |
| goto out_mem_free; |
| } while (len == MAX_LOG_STR_LEN_LOG2); |
| |
| if (len == MIN_LOG_STR_LEN_LOG2) { |
| goto out_mem_free; |
| } else { |
| |
| if (len > MAX_LOG_STR_LEN_LOG2) |
| len = MAX_LOG_STR_LEN_LOG2; |
| |
| ret = m10mo_readl(sd, CATEGORY_LOGLEDFLASH, LOG_STR_ADD3, &addr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| unit_count = len / I2C_MEM_READ_SIZE; |
| for (i = 0; i <= unit_count; i += I2C_MEM_READ_SIZE) { |
| if ((len - i) <= I2C_MEM_READ_SIZE) { |
| ret = m10mo_memory_read(sd, len - i, addr + i, buf); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| vfs_write(fp, buf, len - i, &fp->f_pos); |
| break; |
| } else { |
| ret = m10mo_memory_read(sd, I2C_MEM_READ_SIZE, addr + i, buf); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| vfs_write(fp, buf, I2C_MEM_READ_SIZE, &fp->f_pos); |
| } |
| } |
| } |
| len = MAX_LOG_STR_LEN_LOG2; |
| ptr = ptr + 1; |
| } |
| |
| out_mem_free: |
| kfree(buf); |
| out_close: |
| if (!IS_ERR(fp)) |
| filp_close(fp, current->files); |
| out_file: |
| set_fs(old_fs); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s, dump log error\n", __func__); |
| return ret; |
| } |
| |
| /* Not verified */ |
| int m10mo_dump_string_log2_2(struct v4l2_subdev *sd) |
| { |
| u32 addr, i; |
| mm_segment_t old_fs; |
| struct file *fp; |
| u32 len = MAX_LOG_STR_LEN_LOG2; |
| u32 ret = 0; |
| u32 count = 0; |
| u32 unit_count = 0; |
| u32 ptr = 0; |
| char *buf = NULL; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| char filename[M10MO_FW_LOG_MAX_NAME_LEN] = {0}; |
| |
| m10mo_gen_log_name(filename, M10MO_FW_LOG2_2_NAME); |
| |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| fp = filp_open(filename, |
| O_WRONLY|O_CREAT|O_TRUNC, S_IRUGO|S_IWUGO|S_IXUSR); |
| if (IS_ERR(fp)) { |
| dev_err(&client->dev, |
| "failed to open %s, err %ld\n", |
| M10MO_FW_DUMP_PATH, PTR_ERR(fp)); |
| ret = -ENOENT; |
| goto out_file; |
| } |
| |
| buf = kmalloc(DUMP_BLOCK_SIZE, GFP_KERNEL); |
| if (!buf) { |
| dev_err(&client->dev, "Failed to allocate memory\n"); |
| ret = -ENOMEM; |
| goto out_close; |
| } |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_MODE, LOG_ANALYZE_MODE1); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| while (count++ < MAX_MEM_DUMP_NUM) { |
| ret = m10mo_writew(sd, CATEGORY_LOGLEDFLASH, LOG_SEL1, ptr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_OUTPUT_STR); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| do { |
| ret = m10mo_readw(sd, CATEGORY_LOGLEDFLASH, LOG_DATA_LEN1, &len); |
| if (ret < 0) |
| goto out_mem_free; |
| } while (len == MAX_LOG_STR_LEN_LOG2); |
| |
| if (len == MIN_LOG_STR_LEN_LOG2) { |
| goto out_mem_free; |
| } else { |
| |
| if (len > MAX_LOG_STR_LEN_LOG2) |
| len = MAX_LOG_STR_LEN_LOG2; |
| |
| ret = m10mo_readl(sd, CATEGORY_LOGLEDFLASH, LOG_STR_ADD3, &addr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| unit_count = len / I2C_MEM_READ_SIZE; |
| for (i = 0; i <= unit_count; i += I2C_MEM_READ_SIZE) { |
| if ((len - i) <= I2C_MEM_READ_SIZE) { |
| ret = m10mo_memory_read(sd, len - i, addr + i, buf); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| vfs_write(fp, buf, len - i, &fp->f_pos); |
| break; |
| } else { |
| ret = m10mo_memory_read(sd, I2C_MEM_READ_SIZE, addr + i, buf); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| vfs_write(fp, buf, I2C_MEM_READ_SIZE, &fp->f_pos); |
| } |
| } |
| } |
| len = MAX_LOG_STR_LEN_LOG2; |
| ptr = ptr + 1; |
| } |
| |
| out_mem_free: |
| kfree(buf); |
| out_close: |
| if (!IS_ERR(fp)) |
| filp_close(fp, current->files); |
| out_file: |
| set_fs(old_fs); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s, dump log error\n", __func__); |
| return ret; |
| } |
| |
| int m10mo_dump_string_log2_1(struct v4l2_subdev *sd) |
| { |
| u32 addr, i; |
| mm_segment_t old_fs; |
| struct file *fp; |
| u32 len = MAX_LOG_STR_LEN_LOG2; |
| u32 ret = 0; |
| u32 count = 0; |
| u32 unit_count = 0; |
| u32 ptr = 0; |
| char *buf = NULL; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| char filename[M10MO_FW_LOG_MAX_NAME_LEN] = {0}; |
| |
| m10mo_gen_log_name(filename, M10MO_FW_LOG2_1_NAME); |
| |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| fp = filp_open(filename, |
| O_WRONLY|O_CREAT|O_TRUNC, S_IRUGO|S_IWUGO|S_IXUSR); |
| if (IS_ERR(fp)) { |
| dev_err(&client->dev, |
| "failed to open %s, err %ld\n", |
| M10MO_FW_DUMP_PATH, PTR_ERR(fp)); |
| ret = -ENOENT; |
| goto out_file; |
| } |
| |
| buf = kmalloc(DUMP_BLOCK_SIZE, GFP_KERNEL); |
| if (!buf) { |
| dev_err(&client->dev, "Failed to allocate memory\n"); |
| ret = -ENOMEM; |
| goto out_close; |
| } |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_MODE, LOG_ANALYZE_MODE0); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_DISABLE); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ADD_SHOW, LOG_ADD_SHOW_INIT_VALUE); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| while (count++ < MAX_MEM_DUMP_NUM) { |
| ret = m10mo_writew(sd, CATEGORY_LOGLEDFLASH, LOG_SEL1, ptr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_OUTPUT_STR); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| do { |
| ret = m10mo_readw(sd, CATEGORY_LOGLEDFLASH, LOG_DATA_LEN1, &len); |
| if (ret < 0) |
| goto out_mem_free; |
| } while (len == MAX_LOG_STR_LEN_LOG2); |
| |
| if (len == MIN_LOG_STR_LEN_LOG2) { |
| goto out_mem_free; |
| } else { |
| |
| if (len > MAX_LOG_STR_LEN_LOG2) |
| len = MAX_LOG_STR_LEN_LOG2; |
| |
| ret = m10mo_readl(sd, CATEGORY_LOGLEDFLASH, LOG_STR_ADD3, &addr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| unit_count = len / I2C_MEM_READ_SIZE; |
| for (i = 0; i <= unit_count; i += I2C_MEM_READ_SIZE) { |
| if ((len - i) <= I2C_MEM_READ_SIZE) { |
| ret = m10mo_memory_read(sd, len - i, addr + i, buf); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| vfs_write(fp, buf, len - i, &fp->f_pos); |
| break; |
| } else { |
| ret = m10mo_memory_read(sd, I2C_MEM_READ_SIZE, addr + i, buf); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| vfs_write(fp, buf, I2C_MEM_READ_SIZE, &fp->f_pos); |
| } |
| } |
| } |
| len = MAX_LOG_STR_LEN_LOG2; |
| ptr = ptr + 1; |
| } |
| |
| out_mem_free: |
| kfree(buf); |
| out_close: |
| if (!IS_ERR(fp)) |
| filp_close(fp, current->files); |
| out_file: |
| set_fs(old_fs); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s, dump log error\n", __func__); |
| return ret; |
| } |
| |
| int m10mo_dump_string_log1(struct v4l2_subdev *sd) |
| { |
| u32 addr; |
| mm_segment_t old_fs; |
| struct file *fp; |
| u32 len = MAX_LOG_STR_LEN; |
| u32 ret = 0; |
| u32 count = 0; |
| u32 count_len = 0; |
| u32 ptr = 0; |
| char *buf = NULL; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| char filename[M10MO_FW_LOG_MAX_NAME_LEN] = {0}; |
| |
| m10mo_gen_log_name(filename, M10MO_FW_LOG1_NAME); |
| |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| fp = filp_open(filename, |
| O_WRONLY|O_CREAT|O_TRUNC, S_IRUGO|S_IWUGO|S_IXUSR); |
| if (IS_ERR(fp)) { |
| dev_err(&client->dev, |
| "failed to open %s, err %ld\n", |
| M10MO_FW_DUMP_PATH, PTR_ERR(fp)); |
| ret = -ENOENT; |
| goto out_file; |
| } |
| |
| buf = kmalloc(DUMP_BLOCK_SIZE, GFP_KERNEL); |
| if (!buf) { |
| dev_err(&client->dev, "Failed to allocate memory\n"); |
| ret = -ENOMEM; |
| goto out_close; |
| } |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_MODE, LOG_STANDARD_MODE); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_DISABLE); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ADD_SHOW, LOG_ADD_SHOW_INIT_VALUE); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| while (count++ < MAX_MEM_DUMP_NUM) { |
| ret = m10mo_writew(sd, CATEGORY_LOGLEDFLASH, LOG_SEL1, ptr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_OUTPUT_STR); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| do { |
| ret = m10mo_readb(sd, CATEGORY_LOGLEDFLASH, LOG_STR_LEN, &len); |
| if (ret < 0) |
| goto out_mem_free; |
| msleep(10); |
| count_len++; |
| } while ((len == MAX_LOG_STR_LEN) && (count_len < 10)); |
| |
| if (len == MIN_LOG_STR_LEN) { |
| goto out_mem_free; |
| } else { |
| ret = m10mo_readl(sd, CATEGORY_LOGLEDFLASH, LOG_STR_ADD3, &addr); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| ret = m10mo_memory_read(sd, len, addr, buf); |
| if (ret < 0) |
| goto out_mem_free; |
| |
| buf[len] = '\n'; |
| vfs_write(fp, buf, len + 1, &fp->f_pos); |
| } |
| len = MAX_LOG_STR_LEN; |
| ptr = ptr + 1; |
| } |
| |
| out_mem_free: |
| kfree(buf); |
| out_close: |
| if (!IS_ERR(fp)) |
| filp_close(fp, current->files); |
| out_file: |
| set_fs(old_fs); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s, dump log error\n", __func__); |
| |
| ret = m10mo_writeb(sd, CATEGORY_LOGLEDFLASH, LOG_ACT, LOG_ACT_DISABLE); |
| if (ret < 0) |
| dev_err(&client->dev, "%s, m10mo_writeb error\n", __func__); |
| |
| return ret; |
| } |
| |
| int m10mo_get_fw_address_count(void) |
| { |
| return ARRAY_SIZE(m10mo_fw_address); |
| } |
| |
| int m10mo_get_isp_fw_version_string(struct m10mo_device *dev, |
| char *buf, int len, int fw_address_id) |
| { |
| int err; |
| struct v4l2_subdev *sd = &dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| |
| err = m10mo_to_fw_access_mode(dev); |
| if (err) |
| return err; |
| |
| err = m10mo_writeb(sd, CATEGORY_FLASHROM, |
| REG_FW_READ, REG_FW_READ_CMD_READ); |
| if (err) { |
| dev_err(&client->dev, "Read mode transition fail: %d\n", err); |
| return err; |
| } |
| msleep(10); |
| |
| memset(buf, 0, len); |
| if ((fw_address_id < 0) || |
| (fw_address_id >= ARRAY_SIZE(m10mo_fw_address))) { |
| dev_err(&client->dev, "Error FW address ID: %d\n", |
| fw_address_id); |
| fw_address_id = 0; |
| } |
| err = m10mo_memory_read(sd, len - 1, |
| m10mo_fw_address[fw_address_id], buf); |
| if (err) |
| dev_err(&client->dev, "version read failed\n"); |
| |
| /* Return value checking intentionally omitted */ |
| (void) m10mo_writeb(sd, CATEGORY_FLASHROM, |
| REG_FW_READ, REG_FW_READ_CMD_NONE); |
| return err; |
| } |
| |
| int m10mo_fw_checksum(struct m10mo_device *dev, u16 *result) |
| { |
| int err; |
| struct v4l2_subdev *sd = &dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int res = 0; |
| |
| err = m10mo_setup_flash_controller(sd); |
| if (err) |
| goto leave; |
| |
| err = m10mo_to_fw_access_mode(dev); |
| if (err) |
| goto leave; |
| |
| /* Set start address to 0*/ |
| err = m10mo_set_flash_address(sd, 0x0); |
| if (err) |
| goto leave; |
| |
| /* request checksum */ |
| err = m10mo_writeb(sd, CATEGORY_FLASHROM, REG_FLASH_CHECK, 4); |
| if (err) { |
| dev_err(&client->dev, "Request checksum failed\n"); |
| goto leave; |
| } |
| |
| err = m10mo_wait_operation_complete(sd, REG_FLASH_CHECK, |
| CHECKSUM_TIMEOUT); |
| if (err) |
| goto leave; |
| |
| err = m10mo_readw(sd, CATEGORY_FLASHROM, REG_FLASH_SUM , &res); |
| if (err) { |
| dev_err(&client->dev, "Checksum read failed\n"); |
| goto leave; |
| } |
| *result = (u16)res; |
| leave: |
| return err; |
| } |
| |
| int m10mo_sector_erase_flash(struct m10mo_device *dev, u32 sector_addr) |
| { |
| int ret; |
| struct v4l2_subdev *sd = &dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| |
| /* |
| * Preconditions - system is already in flash access mode, |
| * plls configured |
| */ |
| |
| /* Set start address */ |
| ret = m10mo_set_flash_address(sd, sector_addr); |
| if (ret) |
| return ret; |
| |
| ret = m10mo_writeb(sd, CATEGORY_FLASHROM, |
| REG_FLASH_ERASE, |
| REG_FLASH_ERASE_SECTOR_ERASE); |
| if (ret) { |
| dev_err(&client->dev, "Checksum cmd failed\n"); |
| return ret; |
| } |
| |
| ret = m10mo_wait_operation_complete(sd, REG_FLASH_ERASE, |
| SECTOR_ERASE_TIMEOUT); |
| return ret; |
| } |
| |
| /* Full chip erase */ |
| int m10mo_chip_erase_flash(struct m10mo_device *dev) |
| { |
| struct v4l2_subdev *sd = &dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int ret; |
| /* |
| * Preconditions - system is already in flash access mode, |
| * plls configured |
| */ |
| |
| /* Setup internal RAM */ |
| ret = m10mo_writeb(sd, CATEGORY_FLASHROM, REG_RAM_START, |
| REG_RAM_START_SRAM); |
| if (ret) { |
| dev_err(&client->dev, "Ram setup failed\n"); |
| return ret; |
| } |
| |
| /* Set start address to 0*/ |
| ret = m10mo_set_flash_address(sd, 0x0); |
| if (ret) |
| return ret; |
| |
| /* chip erase command */ |
| ret = m10mo_writeb(sd, CATEGORY_FLASHROM, REG_FLASH_ERASE, |
| REG_FLASH_ERASE_CHIP_ERASE); |
| if (ret) { |
| dev_err(&client->dev, "Chip erase cmd failed\n"); |
| return ret; |
| } |
| ret = m10mo_wait_operation_complete(sd, REG_FLASH_ERASE, |
| CHIP_ERASE_TIMEOUT); |
| return ret; |
| } |
| |
| int m10mo_flash_write_block(struct m10mo_device *dev, u32 target_addr, |
| u8 *block, u32 block_size) |
| { |
| struct v4l2_subdev *sd = &dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| |
| int ret; |
| u32 ram_buffer = SRAM_BUFFER_ADDRESS; |
| int i; |
| |
| ret = m10mo_set_flash_address(sd, target_addr); |
| if (ret) |
| return ret; |
| |
| /* Set block size of 64k == 0 as reg value */ |
| ret = m10mo_writew(sd, CATEGORY_FLASHROM, REG_FLASH_BYTE, 0); |
| if (ret) { |
| dev_err(&client->dev, "Set flash block size failed\n"); |
| return ret; |
| } |
| |
| for (i = 0; i < block_size / ONE_WRITE_SIZE; i++) { |
| ret = m10mo_memory_write(sd, M10MO_MEMORY_WRITE_8BIT, |
| ONE_WRITE_SIZE, |
| ram_buffer, block); |
| if (ret) { |
| /* Retry once */ |
| dev_err(&client->dev, |
| "Write block data send retry\n"); |
| ret = m10mo_memory_write(sd, M10MO_MEMORY_WRITE_8BIT, |
| ONE_WRITE_SIZE, |
| ram_buffer, block); |
| if (ret) { |
| dev_err(&client->dev, |
| "Write block data send failed\n"); |
| return ret; |
| } |
| } |
| ram_buffer += ONE_WRITE_SIZE; |
| block += ONE_WRITE_SIZE; |
| } |
| |
| /* Program block */ |
| ret = m10mo_writeb(sd, CATEGORY_FLASHROM, REG_FLASH_WRITE, |
| REG_FLASH_WRITE_START_PRG); |
| if (ret) { |
| dev_err(&client->dev, "FW program block failed\n"); |
| return ret; |
| } |
| |
| ret = m10mo_wait_operation_complete(sd, REG_FLASH_WRITE, PROGRAMMING_TIMEOUT); |
| |
| return ret; |
| } |
| |
| static int m10mo_sio_write(struct m10mo_device *m10mo_dev, u8 *buf) |
| { |
| int ret; |
| struct v4l2_subdev *sd = &m10mo_dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| |
| if (!m10mo_dev->spi) { |
| dev_err(&client->dev, "No spi device available\n"); |
| return -ENODEV; |
| } |
| |
| /* Set SIO destination address */ |
| ret = m10mo_writel(sd, CATEGORY_FLASHROM, REG_DATA_RAM_ADDR, |
| SDRAM_BUFFER_ADDRESS); |
| if (ret) { |
| dev_err(&client->dev, "sio address setting failed\n"); |
| return ret; |
| } |
| |
| /* Set programming size - multiples of 16 bytes */ |
| ret = m10mo_writel(sd, CATEGORY_FLASHROM, REG_DATA_TRANS_SIZE, |
| FW_SIZE / 16); |
| if (ret) { |
| dev_err(&client->dev, "set program size failed\n"); |
| return ret; |
| } |
| |
| /* Set SDRAM - mystical value from flow picture */ |
| ret = m10mo_writew(sd, CATEGORY_FLASHROM, REG_SDRAM_CFG, 0x0608); |
| if (ret) { |
| dev_err(&client->dev, "set sdram failed\n"); |
| return ret; |
| } |
| |
| /* Set sio mode: */ |
| ret = m10mo_writeb(sd, CATEGORY_FLASHROM, REG_SIO_MODE, |
| REG_SIO_MODE_RISING_LATCH); |
| if (ret) { |
| dev_err(&client->dev, "set sio mode failed\n"); |
| return ret; |
| } |
| |
| /* Start sio mode */ |
| ret = m10mo_writeb(sd, CATEGORY_FLASHROM, REG_RAM_START, |
| REG_RAM_START_SDRAM); |
| if (ret) { |
| dev_err(&client->dev, "start sio mode failed \n"); |
| return ret; |
| } |
| |
| ret = m10mo_wait_operation_complete(sd, REG_RAM_START, |
| STATE_TRANSITION_TIMEOUT); |
| if (ret) |
| return ret; |
| |
| usleep_range(30000, 30000); /* TDB: is that required */ |
| |
| ret = m10mo_dev->spi->write(m10mo_dev->spi->spi_device, |
| buf, FW_SIZE, SIO_BLOCK_SIZE); |
| if (ret) |
| return ret; |
| |
| msleep(5); /* TDB: is that required */ |
| |
| /* Flash address to 0*/ |
| ret = m10mo_set_flash_address(sd, 0); |
| if (ret) |
| return ret; |
| |
| /* Programming size */ |
| ret = m10mo_writel(sd, CATEGORY_FLASHROM, REG_DATA_TRANS_SIZE, FW_SIZE); |
| if (ret) { |
| dev_err(&client->dev, "set sio programming size failed \n"); |
| return ret; |
| } |
| |
| /* Start programming */ |
| ret = m10mo_writeb(sd, CATEGORY_FLASHROM, REG_FLASH_WRITE, |
| REG_FLASH_WRITE_START_PRG); |
| if (ret) { |
| dev_err(&client->dev, "SIO start programming failed\n"); |
| return ret; |
| } |
| ret = m10mo_wait_operation_complete(sd, REG_FLASH_WRITE, |
| PROGRAMMING_TIMEOUT); |
| return ret; |
| } |
| |
| static const struct firmware * |
| m10mo_load_firmware(struct m10mo_device *m10mo_dev) |
| { |
| struct v4l2_subdev *sd = &m10mo_dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| const struct firmware *fw; |
| int i, ret; |
| u16 *fw_ptr, csum = 0; |
| |
| ret = request_firmware(&fw, M10MO_FW_NAME, &client->dev); |
| if (ret) { |
| dev_err(&client->dev, |
| "Error %d while requesting firmware %s\n", |
| ret, M10MO_FW_NAME); |
| return NULL; |
| } |
| |
| if (fw->size != FW_SIZE) { |
| dev_err(&client->dev, |
| "Illegal FW size detected\n"); |
| release_firmware(fw); |
| return NULL; |
| } |
| |
| fw_ptr = (u16 *)fw->data; |
| for (i = 0; i < FW_SIZE/2; i++, fw_ptr++) |
| csum += be16_to_cpup(fw_ptr); |
| |
| if (csum) { |
| dev_err(&client->dev, |
| "Illegal FW csum: %d\n", csum); |
| } |
| |
| return fw; |
| } |
| |
| int m10mo_program_device(struct m10mo_device *m10mo_dev) |
| { |
| struct v4l2_subdev *sd = &m10mo_dev->sd; |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int ret = -ENODEV; |
| u32 i; |
| const struct firmware *fw; |
| |
| dev_info(&client->dev, "Start FW update\n"); |
| |
| fw = m10mo_load_firmware(m10mo_dev); |
| if (!fw) |
| return -ENOENT; |
| |
| ret = m10mo_to_fw_access_mode(m10mo_dev); |
| if (ret) |
| goto release_fw; |
| |
| ret = m10mo_chip_erase_flash(m10mo_dev); |
| if (ret) { |
| dev_err(&client->dev, "Erase failed\n"); |
| goto release_fw; |
| } |
| |
| if (m10mo_dev->spi && m10mo_dev->spi->spi_enabled) { |
| ret = m10mo_sio_write(m10mo_dev, (u8 *)fw->data); |
| if (ret) { |
| dev_err(&client->dev, "Flash write failed\n"); |
| goto release_fw; |
| } |
| } else { |
| for (i = 0 ; i < FW_SIZE; i = i + FLASH_BLOCK_SIZE) { |
| dev_dbg(&client->dev, "Writing block %d\n", i / FLASH_BLOCK_SIZE); |
| ret = m10mo_flash_write_block(m10mo_dev, |
| i, (u8 *)&fw->data[i], |
| FLASH_BLOCK_SIZE); |
| if (ret) { |
| dev_err(&client->dev, "Flash write failed\n"); |
| goto release_fw; |
| } |
| } |
| } |
| |
| dev_info(&client->dev, "Flashing done\n"); |
| msleep(50); |
| |
| ret = 0; |
| |
| release_fw: |
| release_firmware(fw); |
| return ret; |
| } |
| |
| int m10mo_get_spi_state(struct m10mo_device *m10mo_dev) |
| { |
| if (m10mo_dev->spi && m10mo_dev->spi->spi_enabled) |
| return 1; |
| return 0; |
| } |
| |
| int m10mo_set_spi_state(struct m10mo_device *m10mo_dev, bool enabled) |
| { |
| if (m10mo_dev->spi) { |
| m10mo_dev->spi->spi_enabled = !!enabled; |
| return 0; |
| } |
| return -ENODEV; |
| } |
| |
| void m10mo_register_spi_fw_flash_interface(struct m10mo_device *dev, |
| struct m10mo_spi *m10mo_spi_dev) |
| { |
| pr_debug("m10mo: Spi interface registered\n"); |
| dev->spi = m10mo_spi_dev; |
| } |
| EXPORT_SYMBOL_GPL(m10mo_register_spi_fw_flash_interface); |