| /* |
| * intel_mrfl_thermal.c - Intel Merrifield Platform Thermal Driver |
| * |
| * |
| * Copyright (C) 2011 Intel Corporation |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; version 2 of the License. |
| * |
| * 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., |
| * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| * Author: Durgadoss R <durgadoss.r@intel.com> |
| * |
| * DEVICE_NAME: Intel Merrifield platform - PMIC: Thermal Monitor |
| */ |
| |
| #define pr_fmt(fmt) "intel_mrfl_thermal: " fmt |
| |
| #include <linux/pm.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/rpmsg.h> |
| #include <linux/module.h> |
| #include <linux/thermal.h> |
| #include <linux/interrupt.h> |
| #include <linux/completion.h> |
| #include <linux/platform_device.h> |
| |
| #include <asm/intel_scu_pmic.h> |
| #include <asm/intel_mid_rpmsg.h> |
| #include <asm/intel_mid_thermal.h> |
| #include <linux/iio/consumer.h> |
| |
| #define DRIVER_NAME "bcove_thrm" |
| |
| /* Number of Thermal sensors on the PMIC */ |
| #define PMIC_THERMAL_SENSORS 4 |
| |
| /* Registers that govern Thermal Monitoring */ |
| #define THRMMONCFG 0xB3 |
| #define THRMMONCTL 0xB4 |
| #define THRMIRQ 0x04 |
| #define MTHRMIRQ 0x0F |
| #define STHRMIRQ 0xB2 |
| #define IRQLVL1 0x01 |
| #define MIRQLVL1 0x0C |
| #define IRQ_MASK_ALL 0x0F |
| |
| /* PMIC SRAM base address and offset for Thermal register */ |
| #define PMIC_SRAM_BASE_ADDR 0xFFFFF610 |
| #define PMIC_SRAM_THRM_OFFSET 0x03 |
| #define IOMAP_SIZE 0x04 |
| |
| /* NVM BANK REGISTER */ |
| #define EEPROM_CTRL 0x1FE |
| #define EEPROM_REG15 0x1EE |
| #define EEPROM_BANK1_SELECT 0x02 |
| #define EEPROM_BANK1_UNSELECT 0x00 |
| |
| #define PMICALRT (1 << 3) |
| #define SYS2ALRT (1 << 2) |
| #define SYS1ALRT (1 << 1) |
| #define SYS0ALRT (1 << 0) |
| #define THERM_EN (1 << 0) |
| #define THERM_ALRT (1 << 2) |
| |
| /* ADC to Temperature conversion table length */ |
| #define TABLE_LENGTH 34 |
| #define TEMP_INTERVAL 5 |
| |
| /* |
| * LOW event is defined as 0 (implicit) |
| * HIGH event is defined as 1 (implicit) |
| * Hence this event is defined as 2. |
| */ |
| #define EMUL_TEMP_EVENT 2 |
| #define TEMP_WRITE_TIMEOUT (2 * HZ) |
| |
| /* Default _max 85 C */ |
| #define DEFAULT_MAX_TEMP 85 |
| |
| /* Constants defined in BasinCove PMIC spec */ |
| #define PMIC_DIE_ADC_MIN 395 |
| #define PMIC_DIE_ADC_MAX 661 |
| #define PMIC_DIE_TEMP_MIN -40 |
| #define PMIC_DIE_TEMP_MAX 125 |
| #define ADC_VAL_27C 470 |
| #define ADC_COEFFICIENT 675 |
| #define TEMP_OFFSET 27000 |
| |
| /* 'enum' of Thermal sensors */ |
| enum thermal_sensors { SYS0, SYS1, SYS2, PMIC_DIE, _COUNT }; |
| |
| /* |
| * Alert registers store the 'alert' temperature for each sensor, |
| * as 10 bit ADC code. The higher two bits are stored in bits[0:1] of |
| * alert_regs_h. The lower eight bits are stored in alert_regs_l. |
| * The hysteresis value is stored in bits[2:6] of alert_regs_h. |
| * Order: SYS0 SYS1 SYS2 PMIC_DIE |
| * |
| * static const int alert_regs_l[] = { 0xB7, 0xB9, 0xBB, 0xC1 }; |
| */ |
| static const int alert_regs_h[] = { 0xB6, 0xB8, 0xBA, 0xC0 }; |
| |
| /* |
| * ADC code vs Temperature table |
| * This table will be different for different thermistors |
| * Row 0: ADC code |
| * Row 1: Temperature (in degree celsius) |
| */ |
| static const int adc_code[2][TABLE_LENGTH] = { |
| {952, 932, 906, 877, 843, 804, 761, 714, 665, 614, |
| 563, 512, 462, 415, 370, 329, 291, 257, 226, 199, |
| 174, 153, 135, 119, 104, 92, 81, 72, 64, 56, |
| 50, 45, 40, 36}, |
| {-40, -35, -30, -25, -20, -15, -10, -5, 0, 5, |
| 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, |
| 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, |
| 110, 115, 120, 125}, |
| }; |
| |
| static DEFINE_MUTEX(thrm_update_lock); |
| |
| struct thermal_device_info { |
| struct intel_mid_thermal_sensor *sensor; |
| }; |
| |
| struct thermal_data { |
| struct platform_device *pdev; |
| struct iio_channel *iio_chan; |
| struct thermal_zone_device **tzd; |
| struct completion temp_write_complete; |
| void *thrm_addr; |
| unsigned int irq; |
| /* Caching information */ |
| bool is_initialized; |
| unsigned long last_updated; |
| int cached_vals[PMIC_THERMAL_SENSORS]; |
| int num_sensors; |
| int num_virtual_sensors; |
| struct intel_mid_thermal_sensor *sensors; |
| }; |
| static struct thermal_data *tdata; |
| |
| static inline int adc_to_pmic_die_temp(unsigned int val) |
| { |
| /* return temperature in mC */ |
| return (val - ADC_VAL_27C) * ADC_COEFFICIENT + TEMP_OFFSET; |
| } |
| |
| static inline int pmic_die_temp_to_adc(int temp) |
| { |
| /* 'temp' is in C, convert to mC and then do calculations */ |
| return ((temp * 1000) - TEMP_OFFSET) / ADC_COEFFICIENT + ADC_VAL_27C; |
| } |
| |
| /** |
| * find_adc_code - searches the ADC code using binary search |
| * @val: value to find in the array |
| * |
| * This function does binary search on an array sorted in 'descending' order |
| * Can sleep |
| */ |
| static int find_adc_code(uint16_t val) |
| { |
| int left = 0; |
| int right = TABLE_LENGTH - 1; |
| int mid; |
| while (left <= right) { |
| mid = (left + right)/2; |
| if (val == adc_code[0][mid] || |
| (mid > 0 && |
| val > adc_code[0][mid] && val < adc_code[0][mid-1])) |
| return mid; |
| else if (val > adc_code[0][mid]) |
| right = mid - 1; |
| else if (val < adc_code[0][mid]) |
| left = mid + 1; |
| } |
| return -EINVAL; |
| } |
| |
| /** |
| * adc_to_temp - converts the ADC code to temperature in mC |
| * @direct: true if the sensor uses direct conversion |
| * @adc_val: the ADC code to be converted |
| * @tp: temperature return value |
| * |
| * Can sleep |
| */ |
| static int adc_to_temp(int direct, uint16_t adc_val, long *tp) |
| { |
| int x0, x1, y0, y1; |
| int nr, dr; /* Numerator & Denominator */ |
| int indx; |
| int x = adc_val; |
| int8_t pmic_temp_offset; |
| |
| /* Direct conversion for pmic die temperature */ |
| if (direct) { |
| if (adc_val < PMIC_DIE_ADC_MIN || adc_val > PMIC_DIE_ADC_MAX) |
| return -EINVAL; |
| |
| /* An offset added for pmic temp from NVM in TNG B0 */ |
| intel_scu_ipc_iowrite8(EEPROM_CTRL, EEPROM_BANK1_SELECT); |
| intel_scu_ipc_ioread8(EEPROM_REG15, &pmic_temp_offset); |
| intel_scu_ipc_iowrite8(EEPROM_CTRL, EEPROM_BANK1_UNSELECT); |
| |
| adc_val = adc_val + pmic_temp_offset; |
| |
| *tp = adc_to_pmic_die_temp(adc_val); |
| return 0; |
| } |
| |
| indx = find_adc_code(adc_val); |
| if (indx < 0) |
| return -EINVAL; |
| |
| if (adc_code[0][indx] == adc_val) { |
| *tp = adc_code[1][indx] * 1000; |
| return 0; |
| } |
| |
| /* |
| * The ADC code is in between two values directly defined in the |
| * table. So, do linear interpolation to calculate the temperature. |
| */ |
| x0 = adc_code[0][indx]; |
| x1 = adc_code[0][indx - 1]; |
| y0 = adc_code[1][indx]; |
| y1 = adc_code[1][indx - 1]; |
| |
| /* |
| * Find y: |
| * Of course, we can avoid these variables, but keep them |
| * for readability and maintainability. |
| */ |
| nr = (x-x0)*y1 + (x1-x)*y0; |
| dr = x1-x0; |
| |
| if (!dr) |
| return -EINVAL; |
| /* |
| * We have to report the temperature in milli degree celsius. |
| * So, to reduce the loss of precision, do (Nr*1000)/Dr, instead |
| * of (Nr/Dr)*1000. |
| */ |
| *tp = (nr * 1000)/dr; |
| |
| return 0; |
| } |
| |
| /** |
| * temp_to_adc - converts the temperature(in C) to ADC code |
| * @direct: true if the sensor uses direct conversion |
| * @temp: the temperature to be converted |
| * @adc_val: ADC code return value |
| * |
| * Can sleep |
| */ |
| static int temp_to_adc(int direct, int temp, int *adc_val) |
| { |
| int indx; |
| int x0, x1, y0, y1; |
| int nr, dr; /* Numerator & Denominator */ |
| int x = temp; |
| |
| /* Direct conversion for pmic die temperature */ |
| if (direct) { |
| if (temp < PMIC_DIE_TEMP_MIN || temp > PMIC_DIE_TEMP_MAX) |
| return -EINVAL; |
| |
| *adc_val = pmic_die_temp_to_adc(temp); |
| return 0; |
| } |
| |
| if (temp < adc_code[1][0] || temp > adc_code[1][TABLE_LENGTH - 1]) |
| return -EINVAL; |
| |
| |
| /* Find the 'indx' of this 'temp' in the table */ |
| indx = (temp - adc_code[1][0]) / TEMP_INTERVAL; |
| |
| if (temp == adc_code[1][indx]) { |
| *adc_val = adc_code[0][indx]; |
| return 0; |
| } |
| |
| /* |
| * Temperature is not a multiple of 'TEMP_INTERVAL'. So, |
| * do linear interpolation to obtain a better ADC code. |
| */ |
| x0 = adc_code[1][indx]; |
| x1 = adc_code[1][indx + 1]; |
| y0 = adc_code[0][indx]; |
| y1 = adc_code[0][indx + 1]; |
| |
| nr = (x-x0)*y1 + (x1-x)*y0; |
| dr = x1-x0; |
| |
| if (!dr) |
| return -EINVAL; |
| |
| *adc_val = nr/dr; |
| |
| return 0; |
| } |
| |
| /** |
| * set_tmax - sets the given 'adc_val' to the 'alert_reg' |
| * @alert_reg: register address |
| * @adc_val: ADC value to be programmed |
| * |
| * Not protected. Calling function should handle synchronization. |
| * Can sleep |
| */ |
| static int set_tmax(int alert_reg, int adc_val) |
| { |
| int ret; |
| |
| /* Set bits[0:1] of alert_reg_h to bits[8:9] of 'adc_val' */ |
| ret = intel_scu_ipc_update_register(alert_reg, (adc_val >> 8), 0x03); |
| if (ret) |
| return ret; |
| |
| /* Extract bits[0:7] of 'adc_val' and write them into alert_reg_l */ |
| return intel_scu_ipc_iowrite8(alert_reg + 1, adc_val & 0xFF); |
| } |
| |
| /** |
| * program_tmax - programs a default _max value for each sensor |
| * @dev: device pointer |
| * |
| * Can sleep |
| */ |
| static int program_tmax(struct device *dev) |
| { |
| int i, ret; |
| int pmic_die_val, adc_val; |
| |
| ret = temp_to_adc(0, DEFAULT_MAX_TEMP, &adc_val); |
| if (ret) |
| return ret; |
| |
| ret = temp_to_adc(1, DEFAULT_MAX_TEMP, &pmic_die_val); |
| if (ret) |
| return ret; |
| /* |
| * Since this function sets max value, do for all sensors even if |
| * the sensor does not register as a thermal zone. |
| */ |
| for (i = 0; i < PMIC_THERMAL_SENSORS - 1; i++) { |
| ret = set_tmax(alert_regs_h[i], adc_val); |
| if (ret) |
| goto exit_err; |
| } |
| |
| /* Set _max for pmic die sensor */ |
| ret = set_tmax(alert_regs_h[i], pmic_die_val); |
| if (ret) |
| goto exit_err; |
| |
| return ret; |
| |
| exit_err: |
| dev_err(dev, "set_tmax for channel %d failed:%d\n", i, ret); |
| return ret; |
| } |
| |
| static int store_trip_hyst(struct thermal_zone_device *tzd, |
| int trip, long hyst) |
| { |
| int ret; |
| uint8_t data; |
| struct thermal_device_info *td_info = tzd->devdata; |
| int alert_reg = alert_regs_h[td_info->sensor->index]; |
| |
| /* Hysteresis value is 5 bits wide */ |
| if (hyst > 31) |
| return -EINVAL; |
| |
| mutex_lock(&thrm_update_lock); |
| |
| ret = intel_scu_ipc_ioread8(alert_reg, &data); |
| if (ret) |
| goto ipc_fail; |
| |
| /* Set bits [2:6] to value of hyst */ |
| data = (data & 0x83) | (hyst << 2); |
| |
| ret = intel_scu_ipc_iowrite8(alert_reg, data); |
| |
| ipc_fail: |
| mutex_unlock(&thrm_update_lock); |
| return ret; |
| } |
| |
| static int show_trip_hyst(struct thermal_zone_device *tzd, |
| int trip, long *hyst) |
| { |
| int ret; |
| uint8_t data; |
| struct thermal_device_info *td_info = tzd->devdata; |
| int alert_reg = alert_regs_h[td_info->sensor->index]; |
| |
| mutex_lock(&thrm_update_lock); |
| |
| ret = intel_scu_ipc_ioread8(alert_reg, &data); |
| if (!ret) |
| *hyst = (data >> 2) & 0x1F; /* Extract bits[2:6] of data */ |
| |
| mutex_unlock(&thrm_update_lock); |
| |
| return ret; |
| } |
| |
| static int store_trip_temp(struct thermal_zone_device *tzd, |
| int trip, long trip_temp) |
| { |
| int ret, adc_val; |
| struct thermal_device_info *td_info = tzd->devdata; |
| int alert_reg = alert_regs_h[td_info->sensor->index]; |
| |
| if (trip_temp != 0 && trip_temp < 1000) { |
| dev_err(&tzd->device, "Temperature should be in mC\n"); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&thrm_update_lock); |
| |
| /* Convert from mC to C */ |
| trip_temp /= 1000; |
| |
| ret = temp_to_adc(td_info->sensor->direct, (int)trip_temp, &adc_val); |
| if (ret) |
| goto exit; |
| |
| ret = set_tmax(alert_reg, adc_val); |
| exit: |
| mutex_unlock(&thrm_update_lock); |
| return ret; |
| } |
| |
| static int show_trip_temp(struct thermal_zone_device *tzd, |
| int trip, long *trip_temp) |
| { |
| int ret, adc_val; |
| uint8_t l, h; |
| struct thermal_device_info *td_info = tzd->devdata; |
| int alert_reg = alert_regs_h[td_info->sensor->index]; |
| |
| mutex_lock(&thrm_update_lock); |
| |
| ret = intel_scu_ipc_ioread8(alert_reg, &h); |
| if (ret) |
| goto exit; |
| |
| ret = intel_scu_ipc_ioread8(alert_reg + 1, &l); |
| if (ret) |
| goto exit; |
| |
| /* Concatenate 'h' and 'l' to get 10-bit ADC code */ |
| adc_val = ((h & 0x03) << 8) | l; |
| |
| ret = adc_to_temp(td_info->sensor->direct, adc_val, trip_temp); |
| exit: |
| mutex_unlock(&thrm_update_lock); |
| return ret; |
| } |
| |
| static int show_trip_type(struct thermal_zone_device *tzd, |
| int trip, enum thermal_trip_type *trip_type) |
| { |
| /* All are passive trip points */ |
| *trip_type = THERMAL_TRIP_PASSIVE; |
| |
| return 0; |
| } |
| |
| static int show_temp(struct thermal_zone_device *tzd, long *temp) |
| { |
| int ret; |
| struct thermal_device_info *td_info = tzd->devdata; |
| int indx = td_info->sensor->index; |
| |
| if (!tdata->iio_chan) |
| return -EINVAL; |
| |
| mutex_lock(&thrm_update_lock); |
| |
| if (!tdata->is_initialized || |
| time_after(jiffies, tdata->last_updated + HZ)) { |
| ret = iio_read_channel_all_raw(tdata->iio_chan, |
| tdata->cached_vals); |
| if (ret) { |
| dev_err(&tzd->device, "ADC sampling failed:%d\n", ret); |
| goto exit; |
| } |
| tdata->last_updated = jiffies; |
| tdata->is_initialized = true; |
| } |
| |
| ret = adc_to_temp(td_info->sensor->direct, tdata->cached_vals[indx], |
| temp); |
| exit: |
| mutex_unlock(&thrm_update_lock); |
| return ret; |
| } |
| |
| static int show_emul_temp(struct thermal_zone_device *tzd, long *temp) |
| { |
| int ret = 0; |
| char *thermal_event[3]; |
| unsigned long timeout; |
| |
| thermal_event[0] = kasprintf(GFP_KERNEL, "NAME=%s", tzd->type); |
| thermal_event[1] = kasprintf(GFP_KERNEL, "EVENT=%d", EMUL_TEMP_EVENT); |
| thermal_event[2] = NULL; |
| |
| INIT_COMPLETION(tdata->temp_write_complete); |
| kobject_uevent_env(&tzd->device.kobj, KOBJ_CHANGE, thermal_event); |
| |
| timeout = wait_for_completion_timeout(&tdata->temp_write_complete, |
| TEMP_WRITE_TIMEOUT); |
| if (timeout == 0) { |
| /* Waiting timed out */ |
| ret = -ETIMEDOUT; |
| goto exit; |
| } |
| |
| *temp = tzd->emul_temperature; |
| exit: |
| kfree(thermal_event[1]); |
| kfree(thermal_event[0]); |
| return ret; |
| } |
| |
| static int store_emul_temp(struct thermal_zone_device *tzd, |
| unsigned long temp) |
| { |
| tzd->emul_temperature = temp; |
| complete(&tdata->temp_write_complete); |
| return 0; |
| } |
| |
| static int enable_tm(void) |
| { |
| int ret; |
| uint8_t data; |
| |
| mutex_lock(&thrm_update_lock); |
| |
| ret = intel_scu_ipc_ioread8(THRMMONCTL, &data); |
| if (ret) |
| goto ipc_fail; |
| |
| ret = intel_scu_ipc_iowrite8(THRMMONCTL, data | THERM_EN); |
| |
| ipc_fail: |
| mutex_unlock(&thrm_update_lock); |
| return ret; |
| } |
| |
| static struct thermal_device_info *initialize_sensor( |
| struct intel_mid_thermal_sensor *sensor) |
| { |
| struct thermal_device_info *td_info = |
| kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL); |
| |
| if (!td_info) |
| return NULL; |
| |
| td_info->sensor = sensor; |
| |
| init_completion(&tdata->temp_write_complete); |
| return td_info; |
| } |
| |
| static irqreturn_t thermal_intrpt(int irq, void *dev_data) |
| { |
| int ret, sensor, event_type; |
| uint8_t irq_status; |
| unsigned int irq_data; |
| struct thermal_data *tdata = (struct thermal_data *)dev_data; |
| |
| if (!tdata) |
| return IRQ_NONE; |
| |
| mutex_lock(&thrm_update_lock); |
| |
| irq_data = ioread8(tdata->thrm_addr + PMIC_SRAM_THRM_OFFSET); |
| |
| ret = intel_scu_ipc_ioread8(STHRMIRQ, &irq_status); |
| if (ret) |
| goto ipc_fail; |
| |
| dev_dbg(&tdata->pdev->dev, "STHRMIRQ: %.2x\n", irq_status); |
| |
| /* |
| * -1 for invalid interrupt |
| * 1 for LOW to HIGH temperature alert |
| * 0 for HIGH to LOW temperature alert |
| */ |
| event_type = -1; |
| |
| /* Check which interrupt occured and for what event */ |
| if (irq_data & PMICALRT) { |
| event_type = !!(irq_status & PMICALRT); |
| sensor = PMIC_DIE; |
| } else if (irq_data & SYS2ALRT) { |
| event_type = !!(irq_status & SYS2ALRT); |
| sensor = SYS2; |
| } else if (irq_data & SYS1ALRT) { |
| event_type = !!(irq_status & SYS1ALRT); |
| sensor = SYS1; |
| } else if (irq_data & SYS0ALRT) { |
| event_type = !!(irq_status & SYS0ALRT); |
| sensor = SYS0; |
| } else { |
| dev_err(&tdata->pdev->dev, "Invalid Interrupt\n"); |
| ret = IRQ_HANDLED; |
| goto ipc_fail; |
| } |
| |
| if (event_type != -1) { |
| dev_info(&tdata->pdev->dev, |
| "%s interrupt for thermal sensor %d\n", |
| event_type ? "HIGH" : "LOW", sensor); |
| } |
| |
| /* Notify using UEvent */ |
| kobject_uevent(&tdata->pdev->dev.kobj, KOBJ_CHANGE); |
| |
| /* Unmask Thermal Interrupt in the mask register */ |
| ret = intel_scu_ipc_update_register(MIRQLVL1, 0xFF, THERM_ALRT); |
| if (ret) |
| goto ipc_fail; |
| |
| ret = IRQ_HANDLED; |
| |
| ipc_fail: |
| mutex_unlock(&thrm_update_lock); |
| return ret; |
| } |
| |
| static struct thermal_zone_device_ops tzd_emul_ops = { |
| .get_temp = show_emul_temp, |
| .set_emul_temp = store_emul_temp, |
| }; |
| |
| static struct thermal_zone_device_ops tzd_ops = { |
| .get_temp = show_temp, |
| .get_trip_type = show_trip_type, |
| .get_trip_temp = show_trip_temp, |
| .set_trip_temp = store_trip_temp, |
| .get_trip_hyst = show_trip_hyst, |
| .set_trip_hyst = store_trip_hyst, |
| }; |
| |
| static irqreturn_t mrfl_thermal_intrpt_handler(int irq, void* dev_data) |
| { |
| return IRQ_WAKE_THREAD; |
| } |
| |
| static int mrfl_thermal_probe(struct platform_device *pdev) |
| { |
| int i, size, ret; |
| int total_sensors; /* real + virtual sensors */ |
| struct intel_mid_thermal_platform_data *pdata; |
| |
| pdata = pdev->dev.platform_data; |
| if (!pdata) { |
| dev_err(&pdev->dev, "platform data not found\n"); |
| return -EINVAL; |
| } |
| |
| tdata = kzalloc(sizeof(struct thermal_data), GFP_KERNEL); |
| if (!tdata) { |
| dev_err(&pdev->dev, "kzalloc failed\n"); |
| return -ENOMEM; |
| } |
| |
| tdata->pdev = pdev; |
| tdata->num_sensors = pdata->num_sensors; |
| tdata->num_virtual_sensors = pdata->num_virtual_sensors; |
| tdata->sensors = pdata->sensors; |
| tdata->irq = platform_get_irq(pdev, 0); |
| platform_set_drvdata(pdev, tdata); |
| |
| total_sensors = tdata->num_sensors; |
| #ifdef CONFIG_THERMAL_EMULATION |
| total_sensors += tdata->num_virtual_sensors; |
| #endif |
| size = sizeof(struct thermal_zone_device *) * total_sensors; |
| tdata->tzd = kzalloc(size, GFP_KERNEL); |
| if (!tdata->tzd) { |
| dev_err(&pdev->dev, "kzalloc failed\n"); |
| ret = -ENOMEM; |
| goto exit_free; |
| } |
| |
| /* Program a default _max value for each sensor */ |
| ret = program_tmax(&pdev->dev); |
| if (ret) { |
| dev_err(&pdev->dev, "Programming _max failed:%d\n", ret); |
| goto exit_tzd; |
| } |
| |
| /* |
| * Register with IIO to sample temperature values |
| * |
| * Order of the channels obtained from adc: |
| * "SYSTHERM0", "SYSTHERM1", "SYSTHERM2", "PMICDIE" |
| */ |
| tdata->iio_chan = iio_channel_get_all(&pdev->dev); |
| if (tdata->iio_chan == NULL) { |
| dev_err(&pdev->dev, "tdata->iio_chan is null\n"); |
| ret = -EINVAL; |
| goto exit_tzd; |
| } |
| |
| /* Check whether we got all the four channels */ |
| ret = iio_channel_get_num(tdata->iio_chan); |
| if (ret != PMIC_THERMAL_SENSORS) { |
| dev_err(&pdev->dev, "incorrect number of channels:%d\n", ret); |
| ret = -EFAULT; |
| goto exit_iio; |
| } |
| |
| /* Register each sensor with the generic thermal framework */ |
| for (i = 0; i < total_sensors; i++) { |
| if (i < tdata->num_sensors) { |
| tdata->tzd[i] = thermal_zone_device_register( |
| tdata->sensors[i].name, 1, 1, |
| initialize_sensor(&tdata->sensors[i]), |
| &tzd_ops, NULL, 0, 0); |
| } else { |
| tdata->tzd[i] = thermal_zone_device_register( |
| tdata->sensors[i].name, 0, 0, |
| initialize_sensor(&tdata->sensors[i]), |
| &tzd_emul_ops, NULL, 0, 0); |
| } |
| if (IS_ERR(tdata->tzd[i])) { |
| ret = PTR_ERR(tdata->tzd[i]); |
| dev_err(&pdev->dev, |
| "registering thermal sensor %s failed: %d\n", |
| tdata->sensors[i].name, ret); |
| goto exit_reg; |
| } |
| } |
| |
| tdata->thrm_addr = ioremap_nocache(PMIC_SRAM_BASE_ADDR, IOMAP_SIZE); |
| if (!tdata->thrm_addr) { |
| ret = -ENOMEM; |
| dev_err(&pdev->dev, "ioremap_nocache failed\n"); |
| goto exit_reg; |
| } |
| |
| /* Register for Interrupt Handler */ |
| ret = request_threaded_irq(tdata->irq, mrfl_thermal_intrpt_handler, thermal_intrpt, |
| IRQF_TRIGGER_RISING, |
| DRIVER_NAME, tdata); |
| if (ret) { |
| dev_err(&pdev->dev, "request_threaded_irq failed:%d\n", ret); |
| goto exit_ioremap; |
| } |
| |
| /* Enable Thermal Monitoring */ |
| ret = enable_tm(); |
| if (ret) { |
| dev_err(&pdev->dev, "Enabling TM failed:%d\n", ret); |
| goto exit_irq; |
| } |
| |
| return 0; |
| |
| exit_irq: |
| free_irq(tdata->irq, tdata); |
| exit_ioremap: |
| iounmap(tdata->thrm_addr); |
| exit_reg: |
| while (--i >= 0) |
| thermal_zone_device_unregister(tdata->tzd[i]); |
| exit_iio: |
| iio_channel_release_all(tdata->iio_chan); |
| exit_tzd: |
| kfree(tdata->tzd); |
| exit_free: |
| kfree(tdata); |
| return ret; |
| } |
| |
| static int mrfl_thermal_resume(struct device *dev) |
| { |
| dev_info(dev, "resume called.\n"); |
| return 0; |
| } |
| |
| static int mrfl_thermal_suspend(struct device *dev) |
| { |
| dev_info(dev, "suspend called.\n"); |
| return 0; |
| } |
| |
| static int mrfl_thermal_remove(struct platform_device *pdev) |
| { |
| int i, total_sensors; |
| struct thermal_data *tdata = platform_get_drvdata(pdev); |
| |
| if (!tdata) |
| return 0; |
| |
| total_sensors = tdata->num_sensors; |
| |
| #ifdef CONFIG_THERMAL_EMULATION |
| total_sensors += tdata->num_virtual_sensors; |
| #endif |
| |
| for (i = 0; i < total_sensors; i++) |
| thermal_zone_device_unregister(tdata->tzd[i]); |
| |
| free_irq(tdata->irq, tdata); |
| iounmap(tdata->thrm_addr); |
| iio_channel_release_all(tdata->iio_chan); |
| kfree(tdata->tzd); |
| kfree(tdata); |
| return 0; |
| } |
| |
| /********************************************************************* |
| * Driver initialization and finalization |
| *********************************************************************/ |
| |
| static const struct dev_pm_ops thermal_pm_ops = { |
| .suspend = mrfl_thermal_suspend, |
| .resume = mrfl_thermal_resume, |
| }; |
| |
| static struct platform_driver mrfl_thermal_driver = { |
| .driver = { |
| .name = DRIVER_NAME, |
| .owner = THIS_MODULE, |
| .pm = &thermal_pm_ops, |
| }, |
| .probe = mrfl_thermal_probe, |
| .remove = mrfl_thermal_remove, |
| }; |
| |
| static int mrfl_thermal_module_init(void) |
| { |
| return platform_driver_register(&mrfl_thermal_driver); |
| } |
| |
| static void mrfl_thermal_module_exit(void) |
| { |
| platform_driver_unregister(&mrfl_thermal_driver); |
| } |
| |
| /* RPMSG related functionality */ |
| static int mrfl_thermal_rpmsg_probe(struct rpmsg_channel *rpdev) |
| { |
| if (!rpdev) { |
| pr_err("rpmsg channel not created\n"); |
| return -ENODEV; |
| } |
| |
| dev_info(&rpdev->dev, "Probed mrfl_thermal rpmsg device\n"); |
| |
| return mrfl_thermal_module_init(); |
| } |
| |
| static void mrfl_thermal_rpmsg_remove(struct rpmsg_channel *rpdev) |
| { |
| mrfl_thermal_module_exit(); |
| dev_info(&rpdev->dev, "Removed mrfl_thermal rpmsg device\n"); |
| } |
| |
| static void mrfl_thermal_rpmsg_cb(struct rpmsg_channel *rpdev, void *data, |
| int len, void *priv, u32 src) |
| { |
| dev_warn(&rpdev->dev, "unexpected, message\n"); |
| |
| print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1, |
| data, len, true); |
| } |
| |
| static struct rpmsg_device_id mrfl_thermal_id_table[] = { |
| { .name = "rpmsg_mrfl_thermal" }, |
| { }, |
| }; |
| |
| MODULE_DEVICE_TABLE(rpmsg, mrfl_thermal_id_table); |
| |
| static struct rpmsg_driver mrfl_thermal_rpmsg = { |
| .drv.name = DRIVER_NAME, |
| .drv.owner = THIS_MODULE, |
| .probe = mrfl_thermal_rpmsg_probe, |
| .callback = mrfl_thermal_rpmsg_cb, |
| .remove = mrfl_thermal_rpmsg_remove, |
| .id_table = mrfl_thermal_id_table, |
| }; |
| |
| static int __init mrfl_thermal_rpmsg_init(void) |
| { |
| return register_rpmsg_driver(&mrfl_thermal_rpmsg); |
| } |
| |
| static void __exit mrfl_thermal_rpmsg_exit(void) |
| { |
| return unregister_rpmsg_driver(&mrfl_thermal_rpmsg); |
| } |
| |
| module_init(mrfl_thermal_rpmsg_init); |
| module_exit(mrfl_thermal_rpmsg_exit); |
| |
| MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>"); |
| MODULE_DESCRIPTION("Intel Merrifield Platform Thermal Driver"); |
| MODULE_LICENSE("GPL"); |