| /* |
| * Copyright (C) 2011-2014 MediaTek Inc. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /****************************************************************************** |
| * Dependency |
| ******************************************************************************/ |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/types.h> |
| #include <linux/wait.h> |
| #include <linux/slab.h> |
| #include <linux/fs.h> |
| #include <linux/sched/signal.h> |
| #include <linux/poll.h> |
| #include <linux/device.h> |
| #include <linux/interrupt.h> |
| #include <linux/of.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| #include <linux/cdev.h> |
| #include <linux/errno.h> |
| #include <linux/io.h> |
| #include <linux/uaccess.h> |
| /* #include <linux/xlog.h> */ |
| #include <linux/printk.h> |
| #include <linux/semaphore.h> |
| #include <linux/version.h> |
| #include <linux/regulator/consumer.h> |
| |
| static int antSwitchFlag; |
| static struct regulator *vmch_reg; |
| |
| #ifdef pr_fmt |
| #undef pr_fmt |
| #endif |
| #define pr_fmt(fmt) "["KBUILD_MODNAME"]" fmt |
| |
| /****************************************************************************** |
| * Function Configuration |
| ******************************************************************************/ |
| /* #define FAKE_DATA */ |
| #define GPS_SUSPEND_RESUME |
| #define GPS_CONFIGURABLE_RESET_DELAY |
| /****************************************************************************** |
| * Definition |
| ******************************************************************************/ |
| /* device name and major number */ |
| #define GPS_DEVNAME "gps" |
| /****************************************************************************** |
| * structure & enumeration |
| ******************************************************************************/ |
| enum { |
| GPS_PWRCTL_UNSUPPORTED = 0xFF, |
| GPS_PWRCTL_OFF = 0x00, |
| GPS_PWRCTL_ON = 0x01, |
| GPS_PWRCTL_RST = 0x02, |
| GPS_PWRCTL_OFF_FORCE = 0x03, |
| GPS_PWRCTL_RST_FORCE = 0x04, |
| GPS_PWRCTL_MAX = 0x05, |
| }; |
| enum { |
| GPS_PWR_UNSUPPORTED = 0xFF, |
| GPS_PWR_RESUME = 0x00, |
| GPS_PWR_SUSPEND = 0x01, |
| GPS_PWR_MAX = 0x02, |
| }; |
| enum { |
| GPS_STATE_UNSUPPORTED = 0xFF, |
| GPS_STATE_OFF = 0x00, /*cleanup/power off, default state */ |
| GPS_STATE_INIT = 0x01, /*init */ |
| GPS_STATE_START = 0x02, /*start navigating */ |
| GPS_STATE_STOP = 0x03, /*stop navigating */ |
| GPS_STATE_DEC_FREQ = 0x04, |
| GPS_STATE_SLEEP = 0x05, |
| GPS_STATE_MAX = 0x06, |
| }; |
| enum { |
| GPS_PWRSAVE_UNSUPPORTED = 0xFF, |
| GPS_PWRSAVE_DEC_FREQ = 0x00, |
| GPS_PWRSAVE_SLEEP = 0x01, |
| GPS_PWRSAVE_OFF = 0x02, |
| GPS_PWRSAVE_MAX = 0x03, |
| }; |
| /*---------------------------------------------------------------------------*/ |
| struct gps_data { |
| int dat_len; |
| int dat_pos; |
| char dat_buf[4096]; |
| spinlock_t lock; |
| wait_queue_head_t read_wait; |
| struct semaphore sem; |
| }; |
| /*---------------------------------------------------------------------------*/ |
| struct gps_sta_itm { /*gps status record */ |
| unsigned char year; /*current year - 1900 */ |
| unsigned char month; /*1~12 */ |
| unsigned char day; /*1~31 */ |
| unsigned char hour; /*0~23 */ |
| |
| unsigned char minute; /*0~59 */ |
| unsigned char sec; /*0~59 */ |
| unsigned char count; /*reborn count */ |
| unsigned char reason; /*reason: 0: timeout; 1: force */ |
| }; |
| /*---------------------------------------------------------------------------*/ |
| struct gps_sta_obj { |
| int index; |
| struct gps_sta_itm items[32]; |
| }; |
| /*---------------------------------------------------------------------------*/ |
| struct gps_drv_obj { |
| unsigned char pwrctl; |
| unsigned char suspend; |
| unsigned char state; |
| unsigned char pwrsave; |
| int rdelay; /*power reset delay */ |
| struct kobject *kobj; |
| struct mutex sem; |
| struct gps_sta_obj status; |
| struct mt3326_gps_hardware *hw; |
| }; |
| /*---------------------------------------------------------------------------*/ |
| struct gps_dev_obj { |
| struct class *cls; |
| struct device *dev; |
| dev_t devno; |
| struct cdev chdev; |
| struct mt3326_gps_hardware *hw; |
| }; |
| /****************************************************************************** |
| * GPS driver |
| ******************************************************************************/ |
| #ifdef CONFIG_OF |
| static void mt3303_gps_get_dts_data(void); |
| #endif |
| static int mt3303_power_on(struct regulator *, int state); |
| static int mt3303_power_off(struct regulator *, int state); |
| |
| struct mt3326_gps_hardware { |
| int (*ext_power_on)(struct regulator *, int); |
| int (*ext_power_off)(struct regulator *, int); |
| struct regulator *reg_id; |
| }; |
| |
| static struct mt3326_gps_hardware mt3326_gps_hw = { |
| .ext_power_on = mt3303_power_on, |
| .ext_power_off = mt3303_power_off, |
| }; |
| |
| #define min_uV 3300000 |
| #define max_uV 3500000 |
| /****************************************************************************** |
| * local variables |
| ******************************************************************************/ |
| static struct gps_data gps_private = { 0 }; |
| |
| #if defined(FAKE_DATA) |
| static char fake_data[] = { |
| "$GPGGA,135036.000,2446.3713,N,12101.3605,E,1,5,1.61,191.1,M,15.1,M,,*51\r\n" |
| "$GPGSA,A,3,22,18,14,30,31,,,,,,,,1.88,1.61,0.98*09\r\n" |
| "$GPGSV,2,1,6,18,83,106,32,22,58,324,35,30,45,157,35,14,28,308,32*44\r\n" |
| "$GPGSV,2,2,6,40,21,254,,31,17,237,29*42\r\n" |
| "$GPRMC,135036.000,A,2446.37125,N,12101.36054,E,0.243,56.48,140109,,A*46\r\n" |
| "$GPVTG,56.48,T,,M,0.243,N,0.451,K,A*07\r\n" |
| }; |
| #endif /* FAKE_DATA */ |
| |
| /* |
| * this should be synchronous with mnld.c |
| * enum { |
| * MNL_RESTART_NONE = 0x00, //recording the 1st of mnld |
| * MNL_RESTART_TIMEOUT_INIT = 0x01, //restart due to timeout |
| * MNL_RESTART_TIMEOUT_MONITOR = 0x02, //restart due to timeout |
| * MNL_RESTART_TIMEOUT_WAKEUP = 0x03, //restart due to timeout |
| * MNL_RESTART_TIMEOUT_TTFF = 0x04, //restart due to TTFF timeout |
| * MNL_RESTART_FORCE = 0x04, //restart due to external command |
| * }; |
| */ |
| /*---------------------------------------------------------------------------*/ |
| static char *str_reason[] = { |
| "none", |
| "init", |
| "monitor", |
| "wakeup", |
| "TTFF", |
| "force", |
| "unknown" |
| }; |
| |
| /****************************************************************************** |
| * Functions |
| ******************************************************************************/ |
| static inline void mt3326_gps_power(struct mt3326_gps_hardware *hw, |
| unsigned int on, unsigned int force) |
| { |
| /*FIX ME: PM_api should provide a function to get current status */ |
| static unsigned int power_on = 1; |
| int err; |
| |
| pr_info("Switching GPS device %s\n", (on != 0U) ? "on" : "off"); |
| |
| if (hw == NULL) { |
| pr_debug("null pointer!!\n"); |
| return; |
| } |
| |
| if (power_on == on) { |
| pr_info("ignore power control: %d\n", on); |
| } else if (on != 0U) { |
| (void)pr_info("power on: %d\n", on); |
| /*power on */ |
| if (hw->ext_power_on != NULL) { |
| err = hw->ext_power_on(hw->reg_id, 0); |
| if (err != 0) |
| (void)pr_info("ext_power_on fail\n"); |
| } |
| if (hw->ext_power_on != NULL) { |
| err = hw->ext_power_on(hw->reg_id, 1); |
| if (err != 0) |
| (void)pr_info("ext_power_on fail\n"); |
| } |
| mdelay(120UL); |
| } else { |
| pr_info("power off: %d\n", on); |
| if (hw->ext_power_off != NULL) { |
| err = hw->ext_power_off(hw->reg_id, force); |
| if (err != 0) |
| (void)pr_info("ext_power_off fail\n"); |
| } |
| pr_info("power off ok: %d\n", on); |
| } |
| power_on = on; |
| } |
| |
| /*****************************************************************************/ |
| static inline void mt3326_gps_reset(struct mt3326_gps_hardware *hw, |
| int delay, int force) |
| { |
| mt3326_gps_power(hw, 1, 0); |
| mdelay((unsigned long)delay); |
| mt3326_gps_power(hw, 0, (unsigned int)force); |
| mdelay((unsigned long)delay); |
| mt3326_gps_power(hw, 1, 0); |
| } |
| |
| /******************************************************************************/ |
| static inline int mt3326_gps_set_suspend(struct gps_drv_obj *obj, |
| unsigned char suspend) |
| { |
| if (obj == NULL) |
| return -1; |
| mutex_lock(&obj->sem); |
| if (obj->suspend != suspend) { |
| pr_debug("issue sysfs_notify : %p\n", obj->kobj->sd); |
| sysfs_notify(obj->kobj, NULL, "suspend"); |
| } |
| obj->suspend = suspend; |
| mutex_unlock(&obj->sem); |
| return 0; |
| } |
| |
| /******************************************************************************/ |
| static inline int mt3326_gps_set_pwrctl(struct gps_drv_obj *obj, |
| unsigned char pwrctl) |
| { |
| int err = 0; |
| |
| if (obj == NULL) |
| return -1; |
| mutex_lock(&obj->sem); |
| |
| if ((pwrctl == (unsigned char)GPS_PWRCTL_ON) || |
| (pwrctl == (unsigned char)GPS_PWRCTL_OFF)) { |
| obj->pwrctl = pwrctl; |
| mt3326_gps_power(obj->hw, pwrctl, 0); |
| } else if (pwrctl == (unsigned char)GPS_PWRCTL_OFF_FORCE) { |
| obj->pwrctl = pwrctl; |
| mt3326_gps_power(obj->hw, pwrctl, 1); |
| } else if (pwrctl == (unsigned char)GPS_PWRCTL_RST) { |
| mt3326_gps_reset(obj->hw, obj->rdelay, 0); |
| obj->pwrctl = (unsigned char)GPS_PWRCTL_ON; |
| } else if (pwrctl == (unsigned char)GPS_PWRCTL_RST_FORCE) { |
| mt3326_gps_reset(obj->hw, obj->rdelay, 1); |
| obj->pwrctl = (unsigned char)GPS_PWRCTL_ON; |
| } else { |
| err = -1; |
| } |
| mutex_unlock(&obj->sem); |
| return err; |
| } |
| |
| /******************************************************************************/ |
| static inline int mt3326_gps_set_status(struct gps_drv_obj *obj, |
| const char *buf, size_t count) |
| { |
| int err = 0; |
| int year, mon, day, hour, minute, sec, cnt, reason, idx; |
| |
| if (obj == NULL) |
| return -1; |
| |
| mutex_lock(&obj->sem); |
| if (sscanf(buf, "(%d/%d/%d %d:%d:%d) - %d/%d", &year, &mon, &day, |
| &hour, &minute, &sec, &cnt, &reason) == 8) { |
| int number = (int)ARRAY_SIZE(obj->status.items); |
| |
| idx = obj->status.index % number; |
| obj->status.items[idx].year = (unsigned char)year; |
| obj->status.items[idx].month = (unsigned char)mon; |
| obj->status.items[idx].day = (unsigned char)day; |
| obj->status.items[idx].hour = (unsigned char)hour; |
| obj->status.items[idx].minute = (unsigned char)minute; |
| obj->status.items[idx].sec = (unsigned char)sec; |
| obj->status.items[idx].count = (unsigned char)cnt; |
| obj->status.items[idx].reason = (unsigned char)reason; |
| obj->status.index++; |
| } else { |
| err = -1; |
| } |
| mutex_unlock(&obj->sem); |
| return err; |
| } |
| |
| /******************************************************************************/ |
| static inline int mt3326_gps_set_state(struct gps_drv_obj *obj, |
| unsigned char state) |
| { |
| int err = 0; |
| |
| if (obj == NULL) |
| return -1; |
| mutex_lock(&obj->sem); |
| if (state < (unsigned char)GPS_STATE_MAX) |
| obj->state = state; |
| else |
| err = -1; |
| mutex_unlock(&obj->sem); |
| return err; |
| } |
| |
| /******************************************************************************/ |
| static inline int mt3326_gps_set_pwrsave(struct gps_drv_obj *obj, |
| unsigned char pwrsave) |
| { |
| int err = 0; |
| |
| if (obj == NULL) |
| return -1; |
| mutex_lock(&obj->sem); |
| if (pwrsave < (unsigned char)GPS_PWRSAVE_MAX) |
| obj->pwrsave = pwrsave; |
| else |
| err = -1; |
| mutex_unlock(&obj->sem); |
| return err; |
| } |
| |
| /******************************************************************************/ |
| static inline int mt3326_gps_dev_suspend(struct gps_drv_obj *obj) |
| { |
| #if defined(GPS_SUSPEND_RESUME) |
| int err; |
| |
| err = mt3326_gps_set_suspend(obj, GPS_PWR_SUSPEND); |
| if (err != 0) |
| pr_debug("set suspend fail: %d\n", err); |
| err = mt3326_gps_set_pwrctl(obj, GPS_PWRCTL_OFF); |
| if (err != 0) |
| pr_debug("set pwrctl fail: %d\n", err); |
| return err; |
| #endif |
| } |
| |
| /******************************************************************************/ |
| static inline int mt3326_gps_dev_resume(struct gps_drv_obj *obj) |
| { |
| #if defined(GPS_SUSPEND_RESUME) |
| int err; |
| |
| err = mt3326_gps_set_suspend(obj, GPS_PWR_RESUME); |
| if (err != 0) |
| pr_debug("set suspend fail: %d\n", err); |
| /*don't power on device automatically */ |
| return err; |
| #endif |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_show_pwrctl(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct gps_drv_obj *obj; |
| ssize_t res; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| mutex_lock(&obj->sem); |
| res = snprintf(buf, PAGE_SIZE, "%d\n", obj->pwrctl); |
| mutex_unlock(&obj->sem); |
| return res; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_store_pwrctl(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct gps_drv_obj *obj; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| if ((count == (size_t)1) || ((count == (size_t)2) |
| && (buf[1] == '\n'))) { |
| unsigned char pwrctl = (unsigned char)(buf[0] - '0'); |
| |
| if (mt3326_gps_set_pwrctl(obj, pwrctl) == 0) |
| return (ssize_t)count; |
| } |
| return (ssize_t)count; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_show_suspend(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct gps_drv_obj *obj; |
| ssize_t res; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| mutex_lock(&obj->sem); |
| res = snprintf(buf, PAGE_SIZE, "%d\n", obj->suspend); |
| mutex_unlock(&obj->sem); |
| return res; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_store_suspend(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct gps_drv_obj *obj; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| if ((count == (size_t)1) || ((count == (size_t)2) |
| && (buf[1] == '\n'))) { |
| int suspend = buf[0] - '0'; |
| |
| if (suspend == GPS_PWR_SUSPEND) { |
| if (mt3326_gps_dev_suspend(obj) == 0) |
| return (ssize_t)count; |
| } else if (suspend == GPS_PWR_RESUME) { |
| if (mt3326_gps_dev_resume(obj) == 0) |
| return (ssize_t)count; |
| } else { |
| pr_debug("suspend value error: %d!!\n", suspend); |
| return 0; |
| } |
| } |
| return (ssize_t)count; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_show_status(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| int res, idx, num, left, cnt, len; |
| struct gps_drv_obj *obj; |
| char *reason = NULL; |
| int reason_max = (int)ARRAY_SIZE(str_reason); |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| mutex_lock(&obj->sem); |
| num = (int)ARRAY_SIZE(obj->status.items); |
| left = (int)PAGE_SIZE; |
| cnt = 0; |
| len = 0; |
| for (idx = 0; idx < num; idx++) { |
| if (obj->status.items[idx].month == 0) |
| continue; |
| if (obj->status.items[idx].reason >= reason_max) |
| reason = str_reason[reason_max - 1]; |
| else |
| reason = str_reason[obj->status.items[idx].reason]; |
| cnt = snprintf(&buf[len], left, |
| "[%d] %.4d/%.2d/%.2d %.2d:%.2d:%.2d - %d, %s\n", |
| idx, obj->status.items[idx].year + 1900, |
| obj->status.items[idx].month, |
| obj->status.items[idx].day, |
| obj->status.items[idx].hour, |
| obj->status.items[idx].minute, |
| obj->status.items[idx].sec, |
| obj->status.items[idx].count, reason); |
| left -= cnt; |
| len += cnt; |
| } |
| res = PAGE_SIZE - left; |
| mutex_unlock(&obj->sem); |
| return res; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_store_status(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| int res = 0; |
| struct gps_drv_obj *obj; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| res = mt3326_gps_set_status(obj, buf, count); |
| if (res == 0) |
| return (ssize_t)count; |
| |
| pr_debug("invalid content: '%p', length = %zu\n", buf, count); |
| return (ssize_t)count; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_show_state(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| ssize_t res; |
| struct gps_drv_obj *obj; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| mutex_lock(&obj->sem); |
| res = snprintf(buf, PAGE_SIZE, "%d\n", obj->state); |
| mutex_unlock(&obj->sem); |
| return res; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_store_state(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct gps_drv_obj *obj; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| if ((count == (size_t)1) || ((count == (size_t)2) && |
| (buf[1] == '\n'))) { |
| /*To Do: dynamic change according to input */ |
| unsigned char state = (unsigned char)(buf[0] - '0'); |
| |
| if (mt3326_gps_set_state(obj, state) == 0) |
| return (ssize_t)count; |
| } |
| pr_debug("invalid content: '%p', length = %zu\n", buf, count); |
| return (ssize_t)count; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_show_pwrsave(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| ssize_t res; |
| struct gps_drv_obj *obj; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| mutex_lock(&obj->sem); |
| res = snprintf(buf, PAGE_SIZE, "%d\n", obj->pwrsave); |
| mutex_unlock(&obj->sem); |
| return res; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_store_pwrsave(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct gps_drv_obj *obj; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| if ((count == (size_t)1) || ((count == (size_t)2) && |
| (buf[1] == '\n'))) { |
| unsigned char pwrsave = (unsigned char)(buf[0] - '0'); |
| |
| if (mt3326_gps_set_pwrsave(obj, pwrsave) == 0) |
| return (ssize_t)count; |
| } |
| pr_debug("invalid content: '%p', length = %zu\n", buf, count); |
| return (ssize_t)count; |
| } |
| |
| /******************************************************************************/ |
| #if defined(GPS_CONFIGURABLE_RESET_DELAY) |
| /******************************************************************************/ |
| static ssize_t mt3326_show_rdelay(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| ssize_t res; |
| struct gps_drv_obj *obj; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| mutex_lock(&obj->sem); |
| res = snprintf(buf, PAGE_SIZE, "%d\n", obj->rdelay); |
| mutex_unlock(&obj->sem); |
| return res; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_store_rdelay(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct gps_drv_obj *obj; |
| int rdelay; |
| unsigned long val = 0; |
| |
| if (dev == NULL) { |
| pr_debug("dev is null!!\n"); |
| return 0; |
| } |
| obj = (struct gps_drv_obj *)dev_get_drvdata(dev); |
| if (obj == NULL) { |
| pr_debug("drv data is null!!\n"); |
| return 0; |
| } |
| rdelay = (int)kstrtol(buf, 10, &val); |
| if (rdelay == 0 && val <= 2000) { |
| mutex_lock(&obj->sem); |
| obj->rdelay = val; |
| mutex_unlock(&obj->sem); |
| return (ssize_t)count; |
| } |
| pr_debug("invalid content: '%p', length = %zu\n", buf, count); |
| return (ssize_t)count; |
| } |
| |
| /******************************************************************************/ |
| #endif |
| /******************************************************************************/ |
| DEVICE_ATTR(pwrctl, 0664, mt3326_show_pwrctl, mt3326_store_pwrctl); |
| DEVICE_ATTR(suspend, 0664, mt3326_show_suspend, mt3326_store_suspend); |
| DEVICE_ATTR(status, 0664, mt3326_show_status, mt3326_store_status); |
| DEVICE_ATTR(state, 0664, mt3326_show_state, mt3326_store_state); |
| DEVICE_ATTR(pwrsave, 0664, mt3326_show_pwrsave, mt3326_store_pwrsave); |
| #if defined(GPS_CONFIGURABLE_RESET_DELAY) |
| DEVICE_ATTR(rdelay, 0664, mt3326_show_rdelay, mt3326_store_rdelay); |
| #endif |
| static struct device_attribute *gps_attr_list[] = { |
| &dev_attr_pwrctl, |
| &dev_attr_suspend, |
| &dev_attr_status, |
| &dev_attr_state, |
| &dev_attr_pwrsave, |
| #if defined(GPS_CONFIGURABLE_RESET_DELAY) |
| &dev_attr_rdelay, |
| #endif |
| }; |
| |
| /******************************************************************************/ |
| static int mt3326_gps_create_attr(struct device *dev) |
| { |
| int idx, err = 0; |
| int num = (int)ARRAY_SIZE(gps_attr_list); |
| |
| if (dev == NULL) |
| return -EINVAL; |
| |
| for (idx = 0; idx < num; idx++) { |
| err = device_create_file(dev, gps_attr_list[idx]); |
| if (err != 0) { |
| pr_debug("device_create_file (%s) = %d\n", |
| gps_attr_list[idx]->attr.name, err); |
| break; |
| } |
| } |
| |
| return err; |
| } |
| |
| /******************************************************************************/ |
| static int mt3326_gps_delete_attr(struct device *dev) |
| { |
| int idx, err = 0; |
| int num = (int)ARRAY_SIZE(gps_attr_list); |
| |
| if (dev == NULL) |
| return -EINVAL; |
| |
| for (idx = 0; idx < num; idx++) |
| device_remove_file(dev, gps_attr_list[idx]); |
| |
| return err; |
| } |
| |
| /******************************************************************************/ |
| static long mt3326_gps_unlocked_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| pr_debug("%s!!\n", __func__); |
| return -ENOIOCTLCMD; |
| } |
| |
| /******************************************************************************/ |
| static long mt3326_gps_compat_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| long ret; |
| |
| pr_debug("%s!!\n", __func__); |
| ret = mt3326_gps_unlocked_ioctl(file, cmd, arg); |
| return ret; |
| } |
| |
| /*****************************************************************************/ |
| static int mt3326_gps_open(struct inode *inode, struct file *file) |
| { |
| /* all files share the same buffer */ |
| file->private_data = &gps_private; |
| return nonseekable_open(inode, file); |
| } |
| |
| /*****************************************************************************/ |
| static int mt3326_gps_release(struct inode *inode, struct file *file) |
| { |
| struct gps_data *dev = file->private_data; |
| |
| if (dev != NULL) |
| file->private_data = NULL; |
| |
| return 0; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_gps_read(struct file *file, char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct gps_data *dev = file->private_data; |
| ssize_t ret = 0; |
| int copy_len = 0; |
| |
| if (dev == NULL) |
| return -EINVAL; |
| |
| if (signal_pending(current) != 0) |
| return -ERESTARTSYS; |
| |
| if (down_interruptible(&dev->sem) != 0) |
| return -ERESTARTSYS; |
| |
| if (dev->dat_len == 0) { /*no data to be read */ |
| up(&dev->sem); |
| /*non-block mode */ |
| if ((file->f_flags & |
| (unsigned int)O_NONBLOCK) == (unsigned int)O_NONBLOCK) |
| return -EAGAIN; |
| do { /*block mode */ |
| ret = wait_event_interruptible |
| (dev->read_wait, (dev->dat_len > 0)); |
| if (ret == -ERESTARTSYS) |
| return -ERESTARTSYS; |
| } while (ret == 0); |
| if (down_interruptible(&dev->sem) != 0) |
| return -ERESTARTSYS; |
| } |
| |
| /*data is available */ |
| copy_len = (dev->dat_len < (int)count) ? (dev->dat_len) : (int)(count); |
| if (copy_to_user(buf, (dev->dat_buf + dev->dat_pos), |
| (unsigned long)copy_len) != 0UL) { |
| ret = -EFAULT; |
| } else { |
| /*pr_debug("mt3326_gps_read(%ld,%d,%d) = %d\n", |
| *count, dev->dat_pos, dev->dat_len, copy_len); |
| */ |
| if (dev->dat_len > (copy_len + dev->dat_pos)) { |
| dev->dat_pos += copy_len; |
| } else { |
| dev->dat_len = 0; |
| dev->dat_pos = 0; |
| } |
| ret = copy_len; |
| } |
| |
| up(&dev->sem); |
| /* pr_debug("%s return %ld bytes\n", __func__, ret); */ |
| return ret; |
| } |
| |
| /******************************************************************************/ |
| static ssize_t mt3326_gps_write(struct file *file, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct gps_data *dev = file->private_data; |
| ssize_t ret = 0; |
| size_t copy_size = 0; |
| |
| if (dev == NULL) |
| return -EINVAL; |
| |
| if (count == 0UL) /*no data written */ |
| return 0; |
| |
| if (signal_pending(current) != 0) |
| return -ERESTARTSYS; |
| |
| if (down_interruptible(&dev->sem) != 0) |
| return -ERESTARTSYS; |
| |
| copy_size = (count < (size_t)4096) ? count : (size_t)4096; |
| if (copy_from_user(dev->dat_buf, buf, copy_size) != 0UL) { |
| pr_debug("copy_from_user error"); |
| ret = -EFAULT; |
| } else { |
| dev->dat_len = (int)count; |
| dev->dat_pos = 0; |
| ret = (ssize_t)count; |
| } |
| up(&dev->sem); |
| wake_up_interruptible(&dev->read_wait); |
| pr_debug("%s: write %d bytes\n", __func__, dev->dat_len); |
| return ret; |
| } |
| |
| /******************************************************************************/ |
| static unsigned int mt3326_gps_poll(struct file *file, poll_table *wait) |
| { |
| struct gps_data *dev = file->private_data; |
| unsigned int mask = 0; |
| |
| if (dev == NULL) |
| return 0; |
| |
| down(&dev->sem); |
| poll_wait(file, &dev->read_wait, wait); |
| if (dev->dat_len != 0) /*readable if data is available */ |
| mask = (((unsigned int)POLLIN | (unsigned int)POLLRDNORM) |
| | ((unsigned int)POLLOUT | (unsigned int)POLLWRNORM)); |
| else /*always writable */ |
| mask = ((unsigned int)POLLOUT | (unsigned int)POLLWRNORM); |
| up(&dev->sem); |
| pr_debug("%s: mask : 0x%X\n", __func__, mask); |
| return mask; |
| } |
| |
| /*****************************************************************************/ |
| /* Kernel interface */ |
| static const struct file_operations mt3326_gps_fops = { |
| .owner = THIS_MODULE, |
| .unlocked_ioctl = mt3326_gps_unlocked_ioctl, |
| .compat_ioctl = mt3326_gps_compat_ioctl, |
| .open = mt3326_gps_open, |
| .read = mt3326_gps_read, |
| .write = mt3326_gps_write, |
| .release = mt3326_gps_release, |
| .poll = mt3326_gps_poll, |
| }; |
| |
| /*****************************************************************************/ |
| static void mt3326_gps_hw_init(struct mt3326_gps_hardware *hw) |
| { |
| mt3326_gps_power(hw, 1, 0); |
| } |
| |
| /*****************************************************************************/ |
| static void mt3326_gps_hw_exit(struct mt3326_gps_hardware *hw) |
| { |
| mt3326_gps_power(hw, 0, 0); |
| } |
| |
| /*****************************************************************************/ |
| static int mt3326_gps_probe(struct platform_device *dev) |
| { |
| int ret = 0; |
| int err = 0; |
| struct gps_drv_obj *drvobj = NULL; |
| /*struct mt3326_gps_hardware *hw = |
| *(struct mt3326_gps_hardware *)dev->dev.platform_data; |
| */ |
| struct mt3326_gps_hardware *hw = &mt3326_gps_hw; |
| struct gps_dev_obj *devobj = NULL; |
| |
| #ifdef CONFIG_OF |
| mt3303_gps_get_dts_data(); |
| #endif |
| |
| devobj = kzalloc(sizeof(*devobj), GFP_KERNEL); |
| if (devobj == NULL) { |
| /*(void)pr_err("kzalloc fail\n");*/ |
| err = -ENOMEM; |
| return -1; |
| } |
| |
| pr_info("get regulator"); |
| #ifdef CONFIG_MTK_PMIC_CHIP_MT6356 |
| hw->reg_id = regulator_get(&dev->dev, "vcn33_wifi"); |
| #else |
| hw->reg_id = regulator_get(&dev->dev, "vcn33"); |
| #endif |
| if (!hw->reg_id) { |
| pr_info("regulator_get reg_id failed.\n"); |
| return -1; |
| } |
| |
| pr_info("regulator_get_voltage reg_id = %d uV\n", |
| regulator_get_voltage(hw->reg_id)); |
| |
| /*set voltage*/ |
| if (regulator_set_voltage(hw->reg_id, min_uV, max_uV)) { |
| pr_info("regulator_set_voltage reg_id failed.\n"); |
| }; |
| |
| mt3326_gps_hw_init(hw); |
| ret = alloc_chrdev_region(&devobj->devno, 0, 1, GPS_DEVNAME); |
| if (ret != 0) { |
| (void)pr_err("alloc_chrdev_region fail: %d\n", ret); |
| goto error; |
| } else { |
| pr_debug("major: %d, minor: %d\n", MAJOR(devobj->devno), |
| MINOR(devobj->devno)); |
| } |
| cdev_init(&devobj->chdev, &mt3326_gps_fops); |
| devobj->chdev.owner = THIS_MODULE; |
| err = cdev_add(&devobj->chdev, devobj->devno, 1); |
| if (err != 0) { |
| (void)pr_err("cdev_add fail: %d\n", err); |
| goto error; |
| } |
| drvobj = kmalloc(sizeof(*drvobj), GFP_KERNEL); |
| if (drvobj == NULL) { |
| err = -ENOMEM; |
| goto error; |
| } |
| memset(drvobj, 0, sizeof(*drvobj)); |
| |
| devobj->cls = class_create(THIS_MODULE, "gpsdrv"); |
| if (IS_ERR(devobj->cls)) { |
| (void)pr_err("Unable to create class, err = %d\n", |
| (int)PTR_ERR(devobj->cls)); |
| goto error; |
| } |
| devobj->dev = device_create(devobj->cls, NULL, |
| devobj->devno, drvobj, "gps"); |
| drvobj->hw = hw; |
| drvobj->pwrctl = 0; |
| drvobj->suspend = 0; |
| drvobj->state = GPS_STATE_UNSUPPORTED; |
| drvobj->pwrsave = GPS_PWRSAVE_UNSUPPORTED; |
| drvobj->rdelay = 50; |
| drvobj->kobj = &devobj->dev->kobj; |
| mutex_init(&drvobj->sem); |
| |
| err = mt3326_gps_create_attr(devobj->dev); |
| if (err != 0) |
| goto error; |
| |
| /* initialize members */ |
| spin_lock_init(&gps_private.lock); |
| init_waitqueue_head(&gps_private.read_wait); |
| sema_init(&gps_private.sem, 1); |
| |
| gps_private.dat_len = 0; |
| gps_private.dat_pos = 0; |
| (void)memset(gps_private.dat_buf, 0x00, sizeof(gps_private.dat_buf)); |
| |
| /* set platform data: a new device created for gps */ |
| platform_set_drvdata(dev, devobj); |
| |
| if (antSwitchFlag) { |
| pr_info("mt3303 get regulator"); |
| vmch_reg = regulator_get(devobj->dev, "vmch"); |
| if (!vmch_reg) { |
| pr_info("mt3303 regulator_get vmch failed.\n"); |
| return -1; |
| } |
| |
| pr_info("mt3303 regulator_get_voltage reg_id = %d uV\n", |
| regulator_get_voltage(vmch_reg)); |
| } |
| pr_debug("Done\n"); |
| |
| return 0; |
| |
| error: |
| if (err == 0) |
| cdev_del(&devobj->chdev); |
| if (ret == 0) |
| unregister_chrdev_region(devobj->devno, 1); |
| kfree(devobj); |
| kfree(drvobj); |
| |
| return -1; |
| } |
| |
| /*****************************************************************************/ |
| static int mt3326_gps_remove(struct platform_device *dev) |
| { |
| int err; |
| struct gps_dev_obj *devobj = |
| (struct gps_dev_obj *)platform_get_drvdata(dev); |
| struct gps_drv_obj *drvobj; |
| |
| if (devobj == NULL) { |
| (void)pr_err("null pointer: %p\n", devobj); |
| return -1; |
| } |
| |
| drvobj = (struct gps_drv_obj *)dev_get_drvdata(devobj->dev); |
| if (drvobj == NULL) { |
| (void)pr_err("null pointer: %p\n", drvobj); |
| return -1; |
| } |
| pr_debug("Unregistering chardev\n"); |
| |
| cdev_del(&devobj->chdev); |
| unregister_chrdev_region(devobj->devno, 1); |
| |
| mt3326_gps_hw_exit(devobj->hw); |
| err = mt3326_gps_delete_attr(devobj->dev); |
| if (err != 0) |
| pr_debug("delete attr fails: %d\n", err); |
| device_destroy(devobj->cls, devobj->devno); |
| class_destroy(devobj->cls); |
| |
| kfree(devobj); |
| pr_debug("Done\n"); |
| return 0; |
| } |
| |
| /*****************************************************************************/ |
| static void mt3326_gps_shutdown(struct platform_device *dev) |
| { |
| struct gps_dev_obj *devobj = |
| (struct gps_dev_obj *)platform_get_drvdata(dev); |
| |
| pr_debug("Shutting down\n"); |
| mt3326_gps_hw_exit(devobj->hw); |
| } |
| |
| /*****************************************************************************/ |
| #ifdef CONFIG_PM |
| /*****************************************************************************/ |
| static int mt3326_gps_suspend(struct platform_device *dev, |
| pm_message_t state) |
| { |
| int err = 0; |
| struct gps_dev_obj *devobj = |
| (struct gps_dev_obj *)platform_get_drvdata(dev); |
| struct gps_drv_obj *drvobj; |
| |
| if (devobj == NULL) { |
| (void)pr_err("null pointer: %p\n", devobj); |
| return -1; |
| } |
| |
| drvobj = (struct gps_drv_obj *)dev_get_drvdata(devobj->dev); |
| if (drvobj == NULL) { |
| (void)pr_err("null pointer: %p\n", drvobj); |
| return -1; |
| } |
| |
| pr_debug("dev = %p, event = %u,", dev, state.event); |
| if (state.event == PM_EVENT_SUSPEND) |
| err = mt3326_gps_dev_suspend(drvobj); |
| return err; |
| } |
| |
| /*****************************************************************************/ |
| static int mt3326_gps_resume(struct platform_device *dev) |
| { |
| struct gps_dev_obj *devobj = |
| (struct gps_dev_obj *)platform_get_drvdata(dev); |
| struct gps_drv_obj *drvobj = |
| (struct gps_drv_obj *)dev_get_drvdata(devobj->dev); |
| |
| return mt3326_gps_dev_resume(drvobj); |
| } |
| |
| /*****************************************************************************/ |
| #endif /* CONFIG_PM */ |
| /*****************************************************************************/ |
| #ifdef CONFIG_OF |
| static const struct of_device_id apgps_of_ids[] = { |
| {.compatible = "mediatek,mt3303",}, |
| {} |
| }; |
| #endif |
| static struct platform_driver mt3326_gps_driver = { |
| .probe = mt3326_gps_probe, |
| .remove = mt3326_gps_remove, |
| .shutdown = mt3326_gps_shutdown, |
| #if defined(CONFIG_PM) |
| .suspend = mt3326_gps_suspend, |
| .resume = mt3326_gps_resume, |
| #endif |
| .driver = { |
| .name = GPS_DEVNAME, |
| .bus = &platform_bus_type, |
| #ifdef CONFIG_OF |
| .of_match_table = apgps_of_ids, |
| #endif |
| }, |
| }; |
| |
| #ifdef CONFIG_OF |
| static void mt3303_gps_get_dts_data(void) |
| { |
| struct device_node *node = NULL; |
| |
| pr_info("mt3303 before: %d", antSwitchFlag); |
| node = of_find_matching_node(node, apgps_of_ids); |
| if (node) { |
| pr_info("of_property_read_u32"); |
| of_property_read_u32(node, "antSwitchFlag", &antSwitchFlag); |
| } |
| pr_info("mt3303 after: %d", antSwitchFlag); |
| } |
| #endif |
| |
| /*****************************************************************************/ |
| static int __init mt3326_gps_mod_init(void) |
| { |
| int ret = 0; |
| (void)pr_info("%s", __func__); |
| /* ret = driver_register(&mt3326_gps_driver); */ |
| ret = platform_driver_register(&mt3326_gps_driver); |
| |
| return ret; |
| } |
| |
| /*****************************************************************************/ |
| static void __exit mt3326_gps_mod_exit(void) |
| { |
| platform_driver_unregister(&mt3326_gps_driver); |
| } |
| |
| static int mt3303_power_on(struct regulator *reg_id, int state) |
| { |
| int ret; |
| |
| if (antSwitchFlag && vmch_reg) { |
| if (!regulator_is_enabled(vmch_reg)) { |
| if (regulator_enable(vmch_reg)) { |
| pr_info("mt3303 regulator_enable vmch failed\n"); |
| return 1; |
| } |
| } |
| pr_info("mt3303 regulator_enabled vmch is %s\n", |
| regulator_is_enabled(vmch_reg)?"enable":"non-enable"); |
| } |
| |
| if (reg_id) { |
| if (!regulator_is_enabled(reg_id)) { |
| ret = regulator_enable(reg_id); |
| if (ret) { |
| pr_info("mt3303 regulator_enable failed\n"); |
| return 1; |
| } |
| } |
| pr_info("mt3303 regulator_enabled is %s\n", |
| regulator_is_enabled(reg_id)?"enable":"non-enable"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int mt3303_power_off(struct regulator *reg_id, int state) |
| { |
| int ret; |
| |
| if (antSwitchFlag && vmch_reg) { |
| if (regulator_is_enabled(vmch_reg)) { |
| if (regulator_disable(vmch_reg)) { |
| pr_info("mt3303 regulator_disable vmch failed\n"); |
| return 1; |
| } |
| } |
| pr_info("mt3303 regulator_disable vmch is %s\n", |
| regulator_is_enabled(vmch_reg)?"enable":"non-enable"); |
| } |
| |
| if (reg_id) { |
| if (regulator_is_enabled(reg_id)) { |
| ret = regulator_disable(reg_id); |
| if (ret) { |
| pr_info("mt3303 regulator_disable failed\n"); |
| return 1; |
| } |
| } |
| pr_info("mt3303 regulator_disable is %s\n", |
| regulator_is_enabled(reg_id)?"enable":"non-enable"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /*****************************************************************************/ |
| module_init(mt3326_gps_mod_init); |
| module_exit(mt3326_gps_mod_exit); |
| /*****************************************************************************/ |
| MODULE_AUTHOR("MingHsien Hsieh <MingHsien.Hsieh@mediatek.com>"); |
| MODULE_DESCRIPTION("MT3326 GPS Driver"); |
| MODULE_LICENSE("GPL"); |