| /* |
| * OMAP4 Bandgap temperature sensor driver |
| * |
| * Copyright (C) 2011-2012 Texas Instruments Incorporated - http://www.ti.com/ |
| * Author: J Keerthy <j-keerthy@ti.com> |
| * Author: Moiz Sonasath <m-sonasath@ti.com> |
| * Couple of fixes, DT and MFD adaptation: |
| * Eduardo Valentin <eduardo.valentin@ti.com> |
| * |
| * 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, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
| * 02110-1301 USA |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/export.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/interrupt.h> |
| #include <linux/clk.h> |
| #include <linux/gpio.h> |
| #include <linux/platform_device.h> |
| #include <linux/err.h> |
| #include <linux/types.h> |
| #include <linux/mutex.h> |
| #include <linux/reboot.h> |
| #include <linux/of_device.h> |
| #include <linux/of_platform.h> |
| #include <linux/of_irq.h> |
| #include <linux/io.h> |
| |
| #include "omap-bandgap.h" |
| |
| static u32 omap_bandgap_readl(struct omap_bandgap *bg_ptr, u32 reg) |
| { |
| return readl(bg_ptr->base + reg); |
| } |
| |
| static void omap_bandgap_writel(struct omap_bandgap *bg_ptr, u32 val, u32 reg) |
| { |
| writel(val, bg_ptr->base + reg); |
| } |
| |
| static int omap_bandgap_power(struct omap_bandgap *bg_ptr, bool on) |
| { |
| struct temp_sensor_registers *tsr; |
| int i; |
| u32 ctrl; |
| |
| if (!OMAP_BANDGAP_HAS(bg_ptr, POWER_SWITCH)) |
| return 0; |
| |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) { |
| tsr = bg_ptr->conf->sensors[i].registers; |
| ctrl = omap_bandgap_readl(bg_ptr, tsr->temp_sensor_ctrl); |
| ctrl &= ~tsr->bgap_tempsoff_mask; |
| /* active on 0 */ |
| ctrl |= !on << __ffs(tsr->bgap_tempsoff_mask); |
| |
| /* write BGAP_TEMPSOFF should be reset to 0 */ |
| omap_bandgap_writel(bg_ptr, ctrl, tsr->temp_sensor_ctrl); |
| } |
| |
| return 0; |
| } |
| |
| /* This is the Talert handler. Call it only if HAS(TALERT) is set */ |
| static irqreturn_t talert_irq_handler(int irq, void *data) |
| { |
| struct omap_bandgap *bg_ptr = data; |
| struct temp_sensor_registers *tsr; |
| u32 t_hot = 0, t_cold = 0, temp, ctrl; |
| int i; |
| |
| bg_ptr = data; |
| /* Read the status of t_hot */ |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) { |
| tsr = bg_ptr->conf->sensors[i].registers; |
| t_hot = omap_bandgap_readl(bg_ptr, tsr->bgap_status); |
| t_hot &= tsr->status_hot_mask; |
| |
| /* Read the status of t_cold */ |
| t_cold = omap_bandgap_readl(bg_ptr, tsr->bgap_status); |
| t_cold &= tsr->status_cold_mask; |
| |
| if (!t_cold && !t_hot) |
| continue; |
| |
| ctrl = omap_bandgap_readl(bg_ptr, tsr->bgap_mask_ctrl); |
| /* |
| * One TALERT interrupt: Two sources |
| * If the interrupt is due to t_hot then mask t_hot and |
| * and unmask t_cold else mask t_cold and unmask t_hot |
| */ |
| if (t_hot) { |
| ctrl &= ~tsr->mask_hot_mask; |
| ctrl |= tsr->mask_cold_mask; |
| } else if (t_cold) { |
| ctrl &= ~tsr->mask_cold_mask; |
| ctrl |= tsr->mask_hot_mask; |
| } |
| |
| omap_bandgap_writel(bg_ptr, ctrl, tsr->bgap_mask_ctrl); |
| |
| dev_dbg(bg_ptr->dev, |
| "%s: IRQ from %s sensor: hotevent %d coldevent %d\n", |
| __func__, bg_ptr->conf->sensors[i].domain, |
| t_hot, t_cold); |
| |
| /* read temperature */ |
| temp = omap_bandgap_readl(bg_ptr, tsr->temp_sensor_ctrl); |
| temp &= tsr->bgap_dtemp_mask; |
| |
| /* report temperature to whom may concern */ |
| if (bg_ptr->conf->report_temperature) |
| bg_ptr->conf->report_temperature(bg_ptr, i); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* This is the Tshut handler. Call it only if HAS(TSHUT) is set */ |
| static irqreturn_t omap_bandgap_tshut_irq_handler(int irq, void *data) |
| { |
| orderly_poweroff(true); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static |
| int adc_to_temp_conversion(struct omap_bandgap *bg_ptr, int id, int adc_val, |
| int *t) |
| { |
| struct temp_sensor_data *ts_data = bg_ptr->conf->sensors[id].ts_data; |
| |
| /* look up for temperature in the table and return the temperature */ |
| if (adc_val < ts_data->adc_start_val || adc_val > ts_data->adc_end_val) |
| return -ERANGE; |
| |
| *t = bg_ptr->conv_table[adc_val - ts_data->adc_start_val]; |
| |
| return 0; |
| } |
| |
| static int temp_to_adc_conversion(long temp, struct omap_bandgap *bg_ptr, int i, |
| int *adc) |
| { |
| struct temp_sensor_data *ts_data = bg_ptr->conf->sensors[i].ts_data; |
| int high, low, mid; |
| |
| low = 0; |
| high = ts_data->adc_end_val - ts_data->adc_start_val; |
| mid = (high + low) / 2; |
| |
| if (temp < bg_ptr->conv_table[low] || temp > bg_ptr->conv_table[high]) |
| return -EINVAL; |
| |
| while (low < high) { |
| if (temp < bg_ptr->conv_table[mid]) |
| high = mid - 1; |
| else |
| low = mid + 1; |
| mid = (low + high) / 2; |
| } |
| |
| *adc = ts_data->adc_start_val + low; |
| |
| return 0; |
| } |
| |
| /* Talert masks. Call it only if HAS(TALERT) is set */ |
| static int temp_sensor_unmask_interrupts(struct omap_bandgap *bg_ptr, int id, |
| u32 t_hot, u32 t_cold) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 temp, reg_val; |
| |
| /* Read the current on die temperature */ |
| tsr = bg_ptr->conf->sensors[id].registers; |
| temp = omap_bandgap_readl(bg_ptr, tsr->temp_sensor_ctrl); |
| temp &= tsr->bgap_dtemp_mask; |
| |
| reg_val = omap_bandgap_readl(bg_ptr, tsr->bgap_mask_ctrl); |
| if (temp < t_hot) |
| reg_val |= tsr->mask_hot_mask; |
| else |
| reg_val &= ~tsr->mask_hot_mask; |
| |
| if (t_cold < temp) |
| reg_val |= tsr->mask_cold_mask; |
| else |
| reg_val &= ~tsr->mask_cold_mask; |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->bgap_mask_ctrl); |
| |
| return 0; |
| } |
| |
| static |
| int add_hyst(int adc_val, int hyst_val, struct omap_bandgap *bg_ptr, int i, |
| u32 *sum) |
| { |
| int temp, ret; |
| |
| ret = adc_to_temp_conversion(bg_ptr, i, adc_val, &temp); |
| if (ret < 0) |
| return ret; |
| |
| temp += hyst_val; |
| |
| return temp_to_adc_conversion(temp, bg_ptr, i, sum); |
| } |
| |
| /* Talert Thot threshold. Call it only if HAS(TALERT) is set */ |
| static |
| int temp_sensor_configure_thot(struct omap_bandgap *bg_ptr, int id, int t_hot) |
| { |
| struct temp_sensor_data *ts_data = bg_ptr->conf->sensors[id].ts_data; |
| struct temp_sensor_registers *tsr; |
| u32 thresh_val, reg_val; |
| int cold, err = 0; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| |
| /* obtain the T cold value */ |
| thresh_val = omap_bandgap_readl(bg_ptr, tsr->bgap_threshold); |
| cold = (thresh_val & tsr->threshold_tcold_mask) >> |
| __ffs(tsr->threshold_tcold_mask); |
| if (t_hot <= cold) { |
| /* change the t_cold to t_hot - 5000 millidegrees */ |
| err |= add_hyst(t_hot, -ts_data->hyst_val, bg_ptr, id, &cold); |
| /* write the new t_cold value */ |
| reg_val = thresh_val & (~tsr->threshold_tcold_mask); |
| reg_val |= cold << __ffs(tsr->threshold_tcold_mask); |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->bgap_threshold); |
| thresh_val = reg_val; |
| } |
| |
| /* write the new t_hot value */ |
| reg_val = thresh_val & ~tsr->threshold_thot_mask; |
| reg_val |= (t_hot << __ffs(tsr->threshold_thot_mask)); |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->bgap_threshold); |
| if (err) { |
| dev_err(bg_ptr->dev, "failed to reprogram thot threshold\n"); |
| return -EIO; |
| } |
| |
| return temp_sensor_unmask_interrupts(bg_ptr, id, t_hot, cold); |
| } |
| |
| /* Talert Thot and Tcold thresholds. Call it only if HAS(TALERT) is set */ |
| static |
| int temp_sensor_init_talert_thresholds(struct omap_bandgap *bg_ptr, int id, |
| int t_hot, int t_cold) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 reg_val, thresh_val; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| thresh_val = omap_bandgap_readl(bg_ptr, tsr->bgap_threshold); |
| |
| /* write the new t_cold value */ |
| reg_val = thresh_val & ~tsr->threshold_tcold_mask; |
| reg_val |= (t_cold << __ffs(tsr->threshold_tcold_mask)); |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->bgap_threshold); |
| |
| thresh_val = omap_bandgap_readl(bg_ptr, tsr->bgap_threshold); |
| |
| /* write the new t_hot value */ |
| reg_val = thresh_val & ~tsr->threshold_thot_mask; |
| reg_val |= (t_hot << __ffs(tsr->threshold_thot_mask)); |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->bgap_threshold); |
| |
| reg_val = omap_bandgap_readl(bg_ptr, tsr->bgap_mask_ctrl); |
| reg_val |= tsr->mask_hot_mask; |
| reg_val |= tsr->mask_cold_mask; |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->bgap_mask_ctrl); |
| |
| return 0; |
| } |
| |
| /* Talert Tcold threshold. Call it only if HAS(TALERT) is set */ |
| static |
| int temp_sensor_configure_tcold(struct omap_bandgap *bg_ptr, int id, |
| int t_cold) |
| { |
| struct temp_sensor_data *ts_data = bg_ptr->conf->sensors[id].ts_data; |
| struct temp_sensor_registers *tsr; |
| u32 thresh_val, reg_val; |
| int hot, err = 0; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| /* obtain the T cold value */ |
| thresh_val = omap_bandgap_readl(bg_ptr, tsr->bgap_threshold); |
| hot = (thresh_val & tsr->threshold_thot_mask) >> |
| __ffs(tsr->threshold_thot_mask); |
| |
| if (t_cold >= hot) { |
| /* change the t_hot to t_cold + 5000 millidegrees */ |
| err |= add_hyst(t_cold, ts_data->hyst_val, bg_ptr, id, &hot); |
| /* write the new t_hot value */ |
| reg_val = thresh_val & (~tsr->threshold_thot_mask); |
| reg_val |= hot << __ffs(tsr->threshold_thot_mask); |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->bgap_threshold); |
| thresh_val = reg_val; |
| } |
| |
| /* write the new t_cold value */ |
| reg_val = thresh_val & ~tsr->threshold_tcold_mask; |
| reg_val |= (t_cold << __ffs(tsr->threshold_tcold_mask)); |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->bgap_threshold); |
| if (err) { |
| dev_err(bg_ptr->dev, "failed to reprogram tcold threshold\n"); |
| return -EIO; |
| } |
| |
| return temp_sensor_unmask_interrupts(bg_ptr, id, hot, t_cold); |
| } |
| |
| /* This is Tshut Thot config. Call it only if HAS(TSHUT_CONFIG) is set */ |
| static int temp_sensor_configure_tshut_hot(struct omap_bandgap *bg_ptr, |
| int id, int tshut_hot) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 reg_val; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| reg_val = omap_bandgap_readl(bg_ptr, tsr->tshut_threshold); |
| reg_val &= ~tsr->tshut_hot_mask; |
| reg_val |= tshut_hot << __ffs(tsr->tshut_hot_mask); |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->tshut_threshold); |
| |
| return 0; |
| } |
| |
| /* This is Tshut Tcold config. Call it only if HAS(TSHUT_CONFIG) is set */ |
| static int temp_sensor_configure_tshut_cold(struct omap_bandgap *bg_ptr, |
| int id, int tshut_cold) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 reg_val; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| reg_val = omap_bandgap_readl(bg_ptr, tsr->tshut_threshold); |
| reg_val &= ~tsr->tshut_cold_mask; |
| reg_val |= tshut_cold << __ffs(tsr->tshut_cold_mask); |
| omap_bandgap_writel(bg_ptr, reg_val, tsr->tshut_threshold); |
| |
| return 0; |
| } |
| |
| /* This is counter config. Call it only if HAS(COUNTER) is set */ |
| static int configure_temp_sensor_counter(struct omap_bandgap *bg_ptr, int id, |
| u32 counter) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 val; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| val = omap_bandgap_readl(bg_ptr, tsr->bgap_counter); |
| val &= ~tsr->counter_mask; |
| val |= counter << __ffs(tsr->counter_mask); |
| omap_bandgap_writel(bg_ptr, val, tsr->bgap_counter); |
| |
| return 0; |
| } |
| |
| #define bandgap_is_valid(b) \ |
| (!IS_ERR_OR_NULL(b)) |
| #define bandgap_is_valid_sensor_id(b, i) \ |
| ((i) >= 0 && (i) < (b)->conf->sensor_count) |
| static inline int omap_bandgap_validate(struct omap_bandgap *bg_ptr, int id) |
| { |
| if (!bandgap_is_valid(bg_ptr)) { |
| pr_err("%s: invalid bandgap pointer\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (!bandgap_is_valid_sensor_id(bg_ptr, id)) { |
| dev_err(bg_ptr->dev, "%s: sensor id out of range (%d)\n", |
| __func__, id); |
| return -ERANGE; |
| } |
| |
| return 0; |
| } |
| |
| /* Exposed APIs */ |
| /** |
| * omap_bandgap_read_thot() - reads sensor current thot |
| * @bg_ptr - pointer to bandgap instance |
| * @id - sensor id |
| * @thot - resulting current thot value |
| * |
| * returns 0 on success or the proper error code |
| */ |
| int omap_bandgap_read_thot(struct omap_bandgap *bg_ptr, int id, |
| int *thot) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 temp; |
| int ret; |
| |
| ret = omap_bandgap_validate(bg_ptr, id); |
| if (ret) |
| return ret; |
| |
| if (!OMAP_BANDGAP_HAS(bg_ptr, TALERT)) |
| return -ENOTSUPP; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| temp = omap_bandgap_readl(bg_ptr, tsr->bgap_threshold); |
| temp = (temp & tsr->threshold_thot_mask) >> |
| __ffs(tsr->threshold_thot_mask); |
| ret |= adc_to_temp_conversion(bg_ptr, id, temp, &temp); |
| if (ret) { |
| dev_err(bg_ptr->dev, "failed to read thot\n"); |
| return -EIO; |
| } |
| |
| *thot = temp; |
| |
| return 0; |
| } |
| |
| /** |
| * omap_bandgap_write_thot() - sets sensor current thot |
| * @bg_ptr - pointer to bandgap instance |
| * @id - sensor id |
| * @val - desired thot value |
| * |
| * returns 0 on success or the proper error code |
| */ |
| int omap_bandgap_write_thot(struct omap_bandgap *bg_ptr, int id, int val) |
| { |
| struct temp_sensor_data *ts_data; |
| struct temp_sensor_registers *tsr; |
| u32 t_hot; |
| int ret; |
| |
| ret = omap_bandgap_validate(bg_ptr, id); |
| if (ret) |
| return ret; |
| |
| if (!OMAP_BANDGAP_HAS(bg_ptr, TALERT)) |
| return -ENOTSUPP; |
| |
| ts_data = bg_ptr->conf->sensors[id].ts_data; |
| tsr = bg_ptr->conf->sensors[id].registers; |
| |
| if (val < ts_data->min_temp + ts_data->hyst_val) |
| return -EINVAL; |
| ret = temp_to_adc_conversion(val, bg_ptr, id, &t_hot); |
| if (ret < 0) |
| return ret; |
| |
| mutex_lock(&bg_ptr->bg_mutex); |
| temp_sensor_configure_thot(bg_ptr, id, t_hot); |
| mutex_unlock(&bg_ptr->bg_mutex); |
| |
| return 0; |
| } |
| |
| /** |
| * omap_bandgap_read_tcold() - reads sensor current tcold |
| * @bg_ptr - pointer to bandgap instance |
| * @id - sensor id |
| * @tcold - resulting current tcold value |
| * |
| * returns 0 on success or the proper error code |
| */ |
| int omap_bandgap_read_tcold(struct omap_bandgap *bg_ptr, int id, |
| int *tcold) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 temp; |
| int ret; |
| |
| ret = omap_bandgap_validate(bg_ptr, id); |
| if (ret) |
| return ret; |
| |
| if (!OMAP_BANDGAP_HAS(bg_ptr, TALERT)) |
| return -ENOTSUPP; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| temp = omap_bandgap_readl(bg_ptr, tsr->bgap_threshold); |
| temp = (temp & tsr->threshold_tcold_mask) |
| >> __ffs(tsr->threshold_tcold_mask); |
| ret |= adc_to_temp_conversion(bg_ptr, id, temp, &temp); |
| if (ret) |
| return -EIO; |
| |
| *tcold = temp; |
| |
| return 0; |
| } |
| |
| /** |
| * omap_bandgap_write_tcold() - sets the sensor tcold |
| * @bg_ptr - pointer to bandgap instance |
| * @id - sensor id |
| * @val - desired tcold value |
| * |
| * returns 0 on success or the proper error code |
| */ |
| int omap_bandgap_write_tcold(struct omap_bandgap *bg_ptr, int id, int val) |
| { |
| struct temp_sensor_data *ts_data; |
| struct temp_sensor_registers *tsr; |
| u32 t_cold; |
| int ret; |
| |
| ret = omap_bandgap_validate(bg_ptr, id); |
| if (ret) |
| return ret; |
| |
| if (!OMAP_BANDGAP_HAS(bg_ptr, TALERT)) |
| return -ENOTSUPP; |
| |
| ts_data = bg_ptr->conf->sensors[id].ts_data; |
| tsr = bg_ptr->conf->sensors[id].registers; |
| if (val > ts_data->max_temp + ts_data->hyst_val) |
| return -EINVAL; |
| |
| ret = temp_to_adc_conversion(val, bg_ptr, id, &t_cold); |
| if (ret < 0) |
| return ret; |
| |
| mutex_lock(&bg_ptr->bg_mutex); |
| temp_sensor_configure_tcold(bg_ptr, id, t_cold); |
| mutex_unlock(&bg_ptr->bg_mutex); |
| |
| return 0; |
| } |
| |
| /** |
| * omap_bandgap_read_update_interval() - read the sensor update interval |
| * @bg_ptr - pointer to bandgap instance |
| * @id - sensor id |
| * @interval - resulting update interval in miliseconds |
| * |
| * returns 0 on success or the proper error code |
| */ |
| int omap_bandgap_read_update_interval(struct omap_bandgap *bg_ptr, int id, |
| int *interval) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 time; |
| int ret; |
| |
| ret = omap_bandgap_validate(bg_ptr, id); |
| if (ret) |
| return ret; |
| |
| if (!OMAP_BANDGAP_HAS(bg_ptr, COUNTER)) |
| return -ENOTSUPP; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| time = omap_bandgap_readl(bg_ptr, tsr->bgap_counter); |
| if (ret) |
| return ret; |
| time = (time & tsr->counter_mask) >> __ffs(tsr->counter_mask); |
| time = time * 1000 / bg_ptr->clk_rate; |
| |
| *interval = time; |
| |
| return 0; |
| } |
| |
| /** |
| * omap_bandgap_write_update_interval() - set the update interval |
| * @bg_ptr - pointer to bandgap instance |
| * @id - sensor id |
| * @interval - desired update interval in miliseconds |
| * |
| * returns 0 on success or the proper error code |
| */ |
| int omap_bandgap_write_update_interval(struct omap_bandgap *bg_ptr, |
| int id, u32 interval) |
| { |
| int ret = omap_bandgap_validate(bg_ptr, id); |
| if (ret) |
| return ret; |
| |
| if (!OMAP_BANDGAP_HAS(bg_ptr, COUNTER)) |
| return -ENOTSUPP; |
| |
| interval = interval * bg_ptr->clk_rate / 1000; |
| mutex_lock(&bg_ptr->bg_mutex); |
| configure_temp_sensor_counter(bg_ptr, id, interval); |
| mutex_unlock(&bg_ptr->bg_mutex); |
| |
| return 0; |
| } |
| |
| /** |
| * omap_bandgap_read_temperature() - report current temperature |
| * @bg_ptr - pointer to bandgap instance |
| * @id - sensor id |
| * @temperature - resulting temperature |
| * |
| * returns 0 on success or the proper error code |
| */ |
| int omap_bandgap_read_temperature(struct omap_bandgap *bg_ptr, int id, |
| int *temperature) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 temp; |
| int ret; |
| |
| ret = omap_bandgap_validate(bg_ptr, id); |
| if (ret) |
| return ret; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| temp = omap_bandgap_readl(bg_ptr, tsr->temp_sensor_ctrl); |
| temp &= tsr->bgap_dtemp_mask; |
| |
| ret |= adc_to_temp_conversion(bg_ptr, id, temp, &temp); |
| if (ret) |
| return -EIO; |
| |
| *temperature = temp; |
| |
| return 0; |
| } |
| |
| /** |
| * omap_bandgap_set_sensor_data() - helper function to store thermal |
| * framework related data. |
| * @bg_ptr - pointer to bandgap instance |
| * @id - sensor id |
| * @data - thermal framework related data to be stored |
| * |
| * returns 0 on success or the proper error code |
| */ |
| int omap_bandgap_set_sensor_data(struct omap_bandgap *bg_ptr, int id, |
| void *data) |
| { |
| int ret = omap_bandgap_validate(bg_ptr, id); |
| if (ret) |
| return ret; |
| |
| bg_ptr->conf->sensors[id].data = data; |
| |
| return 0; |
| } |
| |
| /** |
| * omap_bandgap_get_sensor_data() - helper function to get thermal |
| * framework related data. |
| * @bg_ptr - pointer to bandgap instance |
| * @id - sensor id |
| * |
| * returns data stored by set function with sensor id on success or NULL |
| */ |
| void *omap_bandgap_get_sensor_data(struct omap_bandgap *bg_ptr, int id) |
| { |
| int ret = omap_bandgap_validate(bg_ptr, id); |
| if (ret) |
| return ERR_PTR(ret); |
| |
| return bg_ptr->conf->sensors[id].data; |
| } |
| |
| static int |
| omap_bandgap_force_single_read(struct omap_bandgap *bg_ptr, int id) |
| { |
| struct temp_sensor_registers *tsr; |
| u32 temp = 0, counter = 1000; |
| |
| tsr = bg_ptr->conf->sensors[id].registers; |
| /* Select single conversion mode */ |
| if (OMAP_BANDGAP_HAS(bg_ptr, MODE_CONFIG)) { |
| temp = omap_bandgap_readl(bg_ptr, tsr->bgap_mode_ctrl); |
| temp &= ~(1 << __ffs(tsr->mode_ctrl_mask)); |
| omap_bandgap_writel(bg_ptr, temp, tsr->bgap_mode_ctrl); |
| } |
| |
| /* Start of Conversion = 1 */ |
| temp = omap_bandgap_readl(bg_ptr, tsr->temp_sensor_ctrl); |
| temp |= 1 << __ffs(tsr->bgap_soc_mask); |
| omap_bandgap_writel(bg_ptr, temp, tsr->temp_sensor_ctrl); |
| /* Wait until DTEMP is updated */ |
| temp = omap_bandgap_readl(bg_ptr, tsr->temp_sensor_ctrl); |
| temp &= (tsr->bgap_dtemp_mask); |
| while ((temp == 0) && --counter) { |
| temp = omap_bandgap_readl(bg_ptr, tsr->temp_sensor_ctrl); |
| temp &= (tsr->bgap_dtemp_mask); |
| } |
| /* Start of Conversion = 0 */ |
| temp = omap_bandgap_readl(bg_ptr, tsr->temp_sensor_ctrl); |
| temp &= ~(1 << __ffs(tsr->bgap_soc_mask)); |
| omap_bandgap_writel(bg_ptr, temp, tsr->temp_sensor_ctrl); |
| |
| return 0; |
| } |
| |
| /** |
| * enable_continuous_mode() - One time enabling of continuous conversion mode |
| * @bg_ptr - pointer to scm instance |
| * |
| * Call this function only if HAS(MODE_CONFIG) is set |
| */ |
| static int enable_continuous_mode(struct omap_bandgap *bg_ptr) |
| { |
| struct temp_sensor_registers *tsr; |
| int i; |
| u32 val; |
| |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) { |
| /* Perform a single read just before enabling continuous */ |
| omap_bandgap_force_single_read(bg_ptr, i); |
| tsr = bg_ptr->conf->sensors[i].registers; |
| val = omap_bandgap_readl(bg_ptr, tsr->bgap_mode_ctrl); |
| val |= 1 << __ffs(tsr->mode_ctrl_mask); |
| omap_bandgap_writel(bg_ptr, val, tsr->bgap_mode_ctrl); |
| } |
| |
| return 0; |
| } |
| |
| static int omap_bandgap_tshut_init(struct omap_bandgap *bg_ptr, |
| struct platform_device *pdev) |
| { |
| int gpio_nr = bg_ptr->tshut_gpio; |
| int status; |
| |
| /* Request for gpio_86 line */ |
| status = gpio_request(gpio_nr, "tshut"); |
| if (status < 0) { |
| dev_err(bg_ptr->dev, |
| "Could not request for TSHUT GPIO:%i\n", 86); |
| return status; |
| } |
| status = gpio_direction_input(gpio_nr); |
| if (status) { |
| dev_err(bg_ptr->dev, |
| "Cannot set input TSHUT GPIO %d\n", gpio_nr); |
| return status; |
| } |
| |
| status = request_irq(gpio_to_irq(gpio_nr), |
| omap_bandgap_tshut_irq_handler, |
| IRQF_TRIGGER_RISING, "tshut", |
| NULL); |
| if (status) { |
| gpio_free(gpio_nr); |
| dev_err(bg_ptr->dev, "request irq failed for TSHUT"); |
| } |
| |
| return 0; |
| } |
| |
| /* Initialization of Talert. Call it only if HAS(TALERT) is set */ |
| static int omap_bandgap_talert_init(struct omap_bandgap *bg_ptr, |
| struct platform_device *pdev) |
| { |
| int ret; |
| |
| bg_ptr->irq = platform_get_irq(pdev, 0); |
| if (bg_ptr->irq < 0) { |
| dev_err(&pdev->dev, "get_irq failed\n"); |
| return bg_ptr->irq; |
| } |
| ret = request_threaded_irq(bg_ptr->irq, NULL, |
| talert_irq_handler, |
| IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
| "talert", bg_ptr); |
| if (ret) { |
| dev_err(&pdev->dev, "Request threaded irq failed.\n"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static const struct of_device_id of_omap_bandgap_match[]; |
| static struct omap_bandgap *omap_bandgap_build(struct platform_device *pdev) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| const struct of_device_id *of_id; |
| struct omap_bandgap *bg_ptr; |
| struct resource *res; |
| u32 prop; |
| int i; |
| |
| /* just for the sake */ |
| if (!node) { |
| dev_err(&pdev->dev, "no platform information available\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| bg_ptr = devm_kzalloc(&pdev->dev, sizeof(struct omap_bandgap), |
| GFP_KERNEL); |
| if (!bg_ptr) { |
| dev_err(&pdev->dev, "Unable to allocate mem for driver ref\n"); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| of_id = of_match_device(of_omap_bandgap_match, &pdev->dev); |
| if (of_id) |
| bg_ptr->conf = of_id->data; |
| |
| i = 0; |
| do { |
| void __iomem *chunk; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, i); |
| if (!res) |
| break; |
| chunk = devm_request_and_ioremap(&pdev->dev, res); |
| if (i == 0) |
| bg_ptr->base = chunk; |
| if (!chunk) { |
| dev_err(&pdev->dev, |
| "failed to request the IO (%d:%pR).\n", |
| i, res); |
| return ERR_PTR(-EADDRNOTAVAIL); |
| } |
| i++; |
| } while (res); |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, TSHUT)) { |
| if (of_property_read_u32(node, "ti,tshut-gpio", &prop) < 0) { |
| dev_err(&pdev->dev, "missing tshut gpio in device tree\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| bg_ptr->tshut_gpio = prop; |
| if (!gpio_is_valid(bg_ptr->tshut_gpio)) { |
| dev_err(&pdev->dev, "invalid gpio for tshut (%d)\n", |
| bg_ptr->tshut_gpio); |
| return ERR_PTR(-EINVAL); |
| } |
| } |
| |
| return bg_ptr; |
| } |
| |
| static |
| int omap_bandgap_probe(struct platform_device *pdev) |
| { |
| struct omap_bandgap *bg_ptr; |
| int clk_rate, ret = 0, i; |
| |
| bg_ptr = omap_bandgap_build(pdev); |
| if (IS_ERR_OR_NULL(bg_ptr)) { |
| dev_err(&pdev->dev, "failed to fetch platform data\n"); |
| return PTR_ERR(bg_ptr); |
| } |
| bg_ptr->dev = &pdev->dev; |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, TSHUT)) { |
| ret = omap_bandgap_tshut_init(bg_ptr, pdev); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "failed to initialize system tshut IRQ\n"); |
| return ret; |
| } |
| } |
| |
| bg_ptr->fclock = clk_get(NULL, bg_ptr->conf->fclock_name); |
| ret = IS_ERR_OR_NULL(bg_ptr->fclock); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to request fclock reference\n"); |
| goto free_irqs; |
| } |
| |
| bg_ptr->div_clk = clk_get(NULL, bg_ptr->conf->div_ck_name); |
| ret = IS_ERR_OR_NULL(bg_ptr->div_clk); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "failed to request div_ts_ck clock ref\n"); |
| goto free_irqs; |
| } |
| |
| bg_ptr->conv_table = bg_ptr->conf->conv_table; |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) { |
| struct temp_sensor_registers *tsr; |
| u32 val; |
| |
| tsr = bg_ptr->conf->sensors[i].registers; |
| /* |
| * check if the efuse has a non-zero value if not |
| * it is an untrimmed sample and the temperatures |
| * may not be accurate |
| */ |
| val = omap_bandgap_readl(bg_ptr, tsr->bgap_efuse); |
| if (ret || !val) |
| dev_info(&pdev->dev, |
| "Non-trimmed BGAP, Temp not accurate\n"); |
| } |
| |
| clk_rate = clk_round_rate(bg_ptr->div_clk, |
| bg_ptr->conf->sensors[0].ts_data->max_freq); |
| if (clk_rate < bg_ptr->conf->sensors[0].ts_data->min_freq || |
| clk_rate == 0xffffffff) { |
| ret = -ENODEV; |
| dev_err(&pdev->dev, "wrong clock rate (%d)\n", clk_rate); |
| goto put_clks; |
| } |
| |
| ret = clk_set_rate(bg_ptr->div_clk, clk_rate); |
| if (ret) |
| dev_err(&pdev->dev, "Cannot re-set clock rate. Continuing\n"); |
| |
| bg_ptr->clk_rate = clk_rate; |
| clk_enable(bg_ptr->fclock); |
| |
| mutex_init(&bg_ptr->bg_mutex); |
| bg_ptr->dev = &pdev->dev; |
| platform_set_drvdata(pdev, bg_ptr); |
| |
| omap_bandgap_power(bg_ptr, true); |
| |
| /* Set default counter to 1 for now */ |
| if (OMAP_BANDGAP_HAS(bg_ptr, COUNTER)) |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) |
| configure_temp_sensor_counter(bg_ptr, i, 1); |
| |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) { |
| struct temp_sensor_data *ts_data; |
| |
| ts_data = bg_ptr->conf->sensors[i].ts_data; |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, TALERT)) |
| temp_sensor_init_talert_thresholds(bg_ptr, i, |
| ts_data->t_hot, |
| ts_data->t_cold); |
| if (OMAP_BANDGAP_HAS(bg_ptr, TSHUT_CONFIG)) { |
| temp_sensor_configure_tshut_hot(bg_ptr, i, |
| ts_data->tshut_hot); |
| temp_sensor_configure_tshut_cold(bg_ptr, i, |
| ts_data->tshut_cold); |
| } |
| } |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, MODE_CONFIG)) |
| enable_continuous_mode(bg_ptr); |
| |
| /* Set .250 seconds time as default counter */ |
| if (OMAP_BANDGAP_HAS(bg_ptr, COUNTER)) |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) |
| configure_temp_sensor_counter(bg_ptr, i, |
| bg_ptr->clk_rate / 4); |
| |
| /* Every thing is good? Then expose the sensors */ |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) { |
| char *domain; |
| |
| if (bg_ptr->conf->sensors[i].register_cooling) |
| bg_ptr->conf->sensors[i].register_cooling(bg_ptr, i); |
| |
| domain = bg_ptr->conf->sensors[i].domain; |
| if (bg_ptr->conf->expose_sensor) |
| bg_ptr->conf->expose_sensor(bg_ptr, i, domain); |
| } |
| |
| /* |
| * Enable the Interrupts once everything is set. Otherwise irq handler |
| * might be called as soon as it is enabled where as rest of framework |
| * is still getting initialised. |
| */ |
| if (OMAP_BANDGAP_HAS(bg_ptr, TALERT)) { |
| ret = omap_bandgap_talert_init(bg_ptr, pdev); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to initialize Talert IRQ\n"); |
| i = bg_ptr->conf->sensor_count; |
| goto disable_clk; |
| } |
| } |
| |
| return 0; |
| |
| disable_clk: |
| clk_disable(bg_ptr->fclock); |
| put_clks: |
| clk_put(bg_ptr->fclock); |
| clk_put(bg_ptr->div_clk); |
| free_irqs: |
| if (OMAP_BANDGAP_HAS(bg_ptr, TSHUT)) { |
| free_irq(gpio_to_irq(bg_ptr->tshut_gpio), NULL); |
| gpio_free(bg_ptr->tshut_gpio); |
| } |
| |
| return ret; |
| } |
| |
| static |
| int omap_bandgap_remove(struct platform_device *pdev) |
| { |
| struct omap_bandgap *bg_ptr = platform_get_drvdata(pdev); |
| int i; |
| |
| /* First thing is to remove sensor interfaces */ |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) { |
| if (bg_ptr->conf->sensors[i].register_cooling) |
| bg_ptr->conf->sensors[i].unregister_cooling(bg_ptr, i); |
| |
| if (bg_ptr->conf->remove_sensor) |
| bg_ptr->conf->remove_sensor(bg_ptr, i); |
| } |
| |
| omap_bandgap_power(bg_ptr, false); |
| |
| clk_disable(bg_ptr->fclock); |
| clk_put(bg_ptr->fclock); |
| clk_put(bg_ptr->div_clk); |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, TALERT)) |
| free_irq(bg_ptr->irq, bg_ptr); |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, TSHUT)) { |
| free_irq(gpio_to_irq(bg_ptr->tshut_gpio), NULL); |
| gpio_free(bg_ptr->tshut_gpio); |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int omap_bandgap_save_ctxt(struct omap_bandgap *bg_ptr) |
| { |
| int i; |
| |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) { |
| struct temp_sensor_registers *tsr; |
| struct temp_sensor_regval *rval; |
| |
| rval = &bg_ptr->conf->sensors[i].regval; |
| tsr = bg_ptr->conf->sensors[i].registers; |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, MODE_CONFIG)) |
| rval->bg_mode_ctrl = omap_bandgap_readl(bg_ptr, |
| tsr->bgap_mode_ctrl); |
| if (OMAP_BANDGAP_HAS(bg_ptr, COUNTER)) |
| rval->bg_counter = omap_bandgap_readl(bg_ptr, |
| tsr->bgap_counter); |
| if (OMAP_BANDGAP_HAS(bg_ptr, TALERT)) { |
| rval->bg_threshold = omap_bandgap_readl(bg_ptr, |
| tsr->bgap_threshold); |
| rval->bg_ctrl = omap_bandgap_readl(bg_ptr, |
| tsr->bgap_mask_ctrl); |
| } |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, TSHUT_CONFIG)) |
| rval->tshut_threshold = omap_bandgap_readl(bg_ptr, |
| tsr->tshut_threshold); |
| } |
| |
| return 0; |
| } |
| |
| static int omap_bandgap_restore_ctxt(struct omap_bandgap *bg_ptr) |
| { |
| int i; |
| |
| for (i = 0; i < bg_ptr->conf->sensor_count; i++) { |
| struct temp_sensor_registers *tsr; |
| struct temp_sensor_regval *rval; |
| u32 val = 0; |
| |
| rval = &bg_ptr->conf->sensors[i].regval; |
| tsr = bg_ptr->conf->sensors[i].registers; |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, COUNTER)) |
| val = omap_bandgap_readl(bg_ptr, tsr->bgap_counter); |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, TSHUT_CONFIG)) |
| omap_bandgap_writel(bg_ptr, |
| rval->tshut_threshold, |
| tsr->tshut_threshold); |
| /* Force immediate temperature measurement and update |
| * of the DTEMP field |
| */ |
| omap_bandgap_force_single_read(bg_ptr, i); |
| |
| if (OMAP_BANDGAP_HAS(bg_ptr, COUNTER)) |
| omap_bandgap_writel(bg_ptr, rval->bg_counter, |
| tsr->bgap_counter); |
| if (OMAP_BANDGAP_HAS(bg_ptr, MODE_CONFIG)) |
| omap_bandgap_writel(bg_ptr, rval->bg_mode_ctrl, |
| tsr->bgap_mode_ctrl); |
| if (OMAP_BANDGAP_HAS(bg_ptr, TALERT)) { |
| omap_bandgap_writel(bg_ptr, |
| rval->bg_threshold, |
| tsr->bgap_threshold); |
| omap_bandgap_writel(bg_ptr, rval->bg_ctrl, |
| tsr->bgap_mask_ctrl); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int omap_bandgap_suspend(struct device *dev) |
| { |
| struct omap_bandgap *bg_ptr = dev_get_drvdata(dev); |
| int err; |
| |
| err = omap_bandgap_save_ctxt(bg_ptr); |
| omap_bandgap_power(bg_ptr, false); |
| clk_disable(bg_ptr->fclock); |
| |
| return err; |
| } |
| |
| static int omap_bandgap_resume(struct device *dev) |
| { |
| struct omap_bandgap *bg_ptr = dev_get_drvdata(dev); |
| |
| clk_enable(bg_ptr->fclock); |
| omap_bandgap_power(bg_ptr, true); |
| |
| return omap_bandgap_restore_ctxt(bg_ptr); |
| } |
| static const struct dev_pm_ops omap_bandgap_dev_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(omap_bandgap_suspend, |
| omap_bandgap_resume) |
| }; |
| |
| #define DEV_PM_OPS (&omap_bandgap_dev_pm_ops) |
| #else |
| #define DEV_PM_OPS NULL |
| #endif |
| |
| static const struct of_device_id of_omap_bandgap_match[] = { |
| #ifdef CONFIG_OMAP4_THERMAL |
| { |
| .compatible = "ti,omap4430-bandgap", |
| .data = (void *)&omap4430_data, |
| }, |
| { |
| .compatible = "ti,omap4460-bandgap", |
| .data = (void *)&omap4460_data, |
| }, |
| { |
| .compatible = "ti,omap4470-bandgap", |
| .data = (void *)&omap4470_data, |
| }, |
| #endif |
| #ifdef CONFIG_OMAP5_THERMAL |
| { |
| .compatible = "ti,omap5430-bandgap", |
| .data = (void *)&omap5430_data, |
| }, |
| #endif |
| /* Sentinel */ |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, of_omap_bandgap_match); |
| |
| static struct platform_driver omap_bandgap_sensor_driver = { |
| .probe = omap_bandgap_probe, |
| .remove = omap_bandgap_remove, |
| .driver = { |
| .name = "omap-bandgap", |
| .pm = DEV_PM_OPS, |
| .of_match_table = of_omap_bandgap_match, |
| }, |
| }; |
| |
| module_platform_driver(omap_bandgap_sensor_driver); |
| |
| MODULE_DESCRIPTION("OMAP4+ bandgap temperature sensor driver"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("platform:omap-bandgap"); |
| MODULE_AUTHOR("Texas Instrument Inc."); |