Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0aa16efe authored by Siddartha Mohanadoss's avatar Siddartha Mohanadoss Committed by Matt Wagantall
Browse files

hwmon: epm_adc: Support EPM driver



Enable EPM device driver.
This snapshot is taken as of msm-3.14 commit 3bc54cf86b
(Merge "msm: camera: Add dummy sub module in sensor pipeline")

Signed-off-by: default avatarSiddartha Mohanadoss <smohanad@codeaurora.org>
parent 8faa0b2e
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
Embedded Power Measurement(EPM) using Cypress Progammable System on a chip (PSOC)

The EPM using the PSoC5 is used by clients to measure on target power
measurement on supported channels. The PSoC5 is a microcontroller
that is communicated over the SPI from the MSM. Primary configuration
supports upto 31 channels and the scope of the driver is to support
userspace clients.

EPM node

Required properties:
- compatible : should be "cy,epm-adc-cy8c5568lti-114" for EPM using PSoC5.
- reg : chip select for the device.
- interrupt-parent : should be phandle of the interrupt controller
		     servicing the interrupt for this device.
- spi-max-frequency : existing support is set for 960kHz.
- qcom,channels : The number of voltage and current channels that
		  are supported.
- qcom,gain : The gain for each of the supported channels.
- qcom,rsense : The rsense value for each channel. The current channels
		rsense values units are in milliohms. The voltage channels
		rsense value is 1.
- qcom,channel-type : Bitmak of channels to set as voltage and current.
		      These are platform dependent and the appropriate scaling
		      functions are used for returning voltage and current.
- qcom,<gpio-name>-gpio : Handle to the GPIO node, see "gpios property" in
		Documentation/devicetree/bindings/gpio/gpio.txt.
  "gpio-name" can be "epm-enable" which is the EPM global enable GPIO for powering up the PSoC.
+8 −0
Original line number Diff line number Diff line
@@ -1137,6 +1137,14 @@ config SENSORS_PCF8591
	  These devices are hard to detect and rarely found on mainstream
	  hardware.  If unsure, say N.

config SENSORS_EPM_ADC
	tristate "EPM ADC Driver for power measurement"
	depends on I2C && SPI_MASTER
	default n
	help
	  Provides interface for measuring the current on specific power rails
	  through the channels on ADC1158 ADC

config SENSORS_QPNP_ADC_VOLTAGE
	tristate "Support for Qualcomm QPNP Voltage ADC"
	depends on MSM_SPMI
+1 −0
Original line number Diff line number Diff line
@@ -153,6 +153,7 @@ obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o
obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
obj-$(CONFIG_SENSORS_EPM_ADC)	+= epm_adc.o
obj-$(CONFIG_SENSORS_QPNP_ADC_VOLTAGE)	+= qpnp-adc-voltage.o qpnp-adc-common.o
obj-$(CONFIG_SENSORS_QPNP_ADC_CURRENT)	+= qpnp-adc-current.o qpnp-adc-common.o

+484 −0
Original line number Diff line number Diff line
/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/hwmon.h>
#include <linux/delay.h>
#include <linux/epm_adc.h>
#include <linux/uaccess.h>
#include <linux/spi/spi.h>
#include <linux/hwmon-sysfs.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>

#define EPM_ADC_DRIVER_NAME		"epm_adc"
#define EPM_ADC_MAX_FNAME		20
#define EPM_ADC_CONVERSION_DELAY	100 /* milliseconds */

#define EPM_ADC_SPI_BITS_PER_WORD	8
#define GPIO_EPM_GLOBAL_ENABLE		86
#define GPIO_EPM_MARKER1		96
#define GPIO_EPM_MARKER2		85
#define EPM_ADC_CONVERSION_TIME_MIN	50000
#define EPM_ADC_CONVERSION_TIME_MAX	51000
/* PSoc Commands */

#define EPM_PSOC_GLOBAL_ENABLE				81
#define EPM_PSOC_VREF_VOLTAGE				2048
#define EPM_PSOC_MAX_ADC_CODE_15_BIT			32767
#define EPM_PSOC_MAX_ADC_CODE_12_BIT			4096
#define EPM_GLOBAL_ENABLE_MIN_DELAY			5000
#define EPM_GLOBAL_ENABLE_MAX_DELAY			5100

struct epm_adc_drv {
	struct platform_device		*pdev;
	struct device			*hwmon;
	struct spi_device		*epm_spi_client;
	struct mutex			conv_lock;
	uint32_t			bus_id;
	struct miscdevice		misc;
	uint32_t			channel_mask;
	uint32_t			epm_global_en_gpio;
	struct epm_chan_properties	epm_psoc_ch_prop[0];
};

static struct epm_adc_drv *epm_adc_drv;

static int epm_adc_psoc_gpio_init(struct epm_adc_drv *epm_adc,
							bool enable)
{
	int rc = 0;

	if (enable) {
		rc = gpio_request(epm_adc->epm_global_en_gpio,
							"EPM_PSOC_GLOBAL_EN");
		if (!rc) {
			gpio_direction_output(epm_adc->epm_global_en_gpio, 1);
		} else {
			pr_err("%s: Configure EPM_GLOBAL_EN Failed\n",
								__func__);
			return rc;
		}
	} else {
		gpio_direction_output(epm_adc->epm_global_en_gpio, 0);
		gpio_free(epm_adc->epm_global_en_gpio);
	}

	return 0;
}

static int epm_request_marker1(void)
{
	int rc = 0;

	rc = gpio_request(GPIO_EPM_MARKER1, "EPM_MARKER1");
	if (!rc) {
		gpio_direction_output(GPIO_EPM_MARKER1, 1);
	} else {
		pr_err("%s: Configure MARKER1 GPIO Failed\n",
							__func__);
		return rc;
	}

	return 0;
}

static int epm_set_marker1(struct epm_marker_level *marker_init)
{
	gpio_set_value(GPIO_EPM_MARKER1, marker_init->level);

	return 0;
}

static int epm_request_marker2(void)
{
	int rc = 0;

	rc = gpio_request(GPIO_EPM_MARKER2, "EPM_MARKER2");
	if (!rc) {
		gpio_direction_output(GPIO_EPM_MARKER2, 1);
	} else {
		pr_err("%s: Configure MARKER2 GPIO Failed\n",
							__func__);
		return rc;
	}

	return 0;
}

static int epm_set_marker2(struct epm_marker_level *marker_init)
{
	gpio_set_value(GPIO_EPM_MARKER2, marker_init->level);

	return 0;
}

static int epm_marker1_release(void)
{
	gpio_free(GPIO_EPM_MARKER1);

	return 0;
}

static int epm_marker2_release(void)
{
	gpio_free(GPIO_EPM_MARKER2);

	return 0;
}

static int epm_psoc_generic_request(struct epm_adc_drv *epm_adc,
			struct epm_generic_request *psoc_get_data)
{
	struct spi_message m;
	struct spi_transfer t;
	char tx_buf[64], rx_buf[64];
	int rc = 0, data_loop = 0;

	spi_setup(epm_adc->epm_spi_client);

	memset(&t, 0, sizeof(t));
	memset(tx_buf, 0, sizeof(tx_buf));
	memset(rx_buf, 0, sizeof(tx_buf));
	t.tx_buf = tx_buf;
	t.rx_buf = rx_buf;
	spi_message_init(&m);
	spi_message_add_tail(&t, &m);

	for (data_loop = 0; data_loop < 64; data_loop++)
		tx_buf[data_loop] = psoc_get_data->buf[data_loop];

	t.len = sizeof(tx_buf);
	t.bits_per_word = EPM_ADC_SPI_BITS_PER_WORD;

	rc = spi_sync(epm_adc->epm_spi_client, &m);
	if (rc)
		return rc;

	for (data_loop = 0; data_loop < 64; data_loop++)
		psoc_get_data->buf[data_loop] = rx_buf[data_loop];

	return rc;
}

static long epm_adc_ioctl(struct file *file, unsigned int cmd,
						unsigned long arg)
{
	struct epm_adc_drv *epm_adc = epm_adc_drv;

	switch (cmd) {
	case EPM_MARKER1_REQUEST:
		{
			uint32_t result;
			result = epm_request_marker1();

			if (copy_to_user((void __user *)arg, &result,
						sizeof(uint32_t)))
				return -EFAULT;
			break;
		}
	case EPM_MARKER2_REQUEST:
		{
			uint32_t result;
			result = epm_request_marker2();

			if (copy_to_user((void __user *)arg, &result,
						sizeof(uint32_t)))
				return -EFAULT;
			break;
		}
	case EPM_MARKER1_SET_LEVEL:
		{
			struct epm_marker_level marker_init;
			uint32_t result;

			if (copy_from_user(&marker_init, (void __user *)arg,
					sizeof(struct epm_marker_level)))
				return -EFAULT;

			result = epm_set_marker1(&marker_init);

			if (copy_to_user((void __user *)arg, &result,
						sizeof(uint32_t)))
				return -EFAULT;
			break;
		}
	case EPM_MARKER2_SET_LEVEL:
		{
			struct epm_marker_level marker_init;
			uint32_t result;

			if (copy_from_user(&marker_init, (void __user *)arg,
					sizeof(struct epm_marker_level)))
				return -EFAULT;

			result = epm_set_marker2(&marker_init);

			if (copy_to_user((void __user *)arg, &result,
						sizeof(uint32_t)))
				return -EFAULT;
			break;
		}
	case EPM_MARKER1_RELEASE:
		{
			uint32_t result;
			result = epm_marker1_release();

			if (copy_to_user((void __user *)arg, &result,
						sizeof(uint32_t)))
				return -EFAULT;
			break;
		}
	case EPM_MARKER2_RELEASE:
		{
			uint32_t result;
			result = epm_marker2_release();

			if (copy_to_user((void __user *)arg, &result,
						sizeof(uint32_t)))
				return -EFAULT;
			break;
		}
	case EPM_PSOC_ADC_INIT:
		{
			int rc;

			rc = epm_adc_psoc_gpio_init(epm_adc, true);
			if (rc)
				pr_err("GPIO init failed with %d\n", rc);

			if (copy_to_user((void __user *)arg, &rc,
						sizeof(int)))
				return -EFAULT;
			break;
		}
	case EPM_PSOC_ADC_DEINIT:
		{
			int rc;
			rc = epm_adc_psoc_gpio_init(epm_adc, false);

			if (copy_to_user((void __user *)arg, &rc,
						sizeof(int)))
				return -EFAULT;
			break;
		}
	case EPM_PSOC_GENERIC_REQUEST:
		{
			struct epm_generic_request psoc_get_data;
			int rc;

			if (copy_from_user(&psoc_get_data,
				(void __user *)arg,
				sizeof(struct
				epm_generic_request)))
				return -EFAULT;

			rc = epm_psoc_generic_request(epm_adc, &psoc_get_data);
			if (rc)
				pr_err("Generic request failed\n");

			if (copy_to_user((void __user *)arg, &psoc_get_data,
				sizeof(struct
				epm_generic_request)))
				return -EFAULT;
			break;
		}
	default:
		return -EINVAL;
	}

	return 0;
}

const struct file_operations epm_adc_fops = {
	.unlocked_ioctl = epm_adc_ioctl,
};

static int get_device_tree_data(struct spi_device *spi)
{
	const struct device_node *node = spi->dev.of_node;
	struct epm_adc_drv *epm_adc;
	u32 *epm_ch_gain, *epm_ch_rsense;
	u32 rc = 0, epm_num_channels, i, channel_mask, epm_gpio_num;

	if (!node)
		return -EINVAL;

	rc = of_property_read_u32(node,
			"qcom,channels", &epm_num_channels);
	if (rc) {
		dev_err(&spi->dev, "missing channel numbers\n");
		return -ENODEV;
	}

	epm_ch_gain = devm_kzalloc(&spi->dev,
			epm_num_channels * sizeof(u32), GFP_KERNEL);
	if (!epm_ch_gain) {
		dev_err(&spi->dev, "cannot allocate gain\n");
		return -ENOMEM;
	}

	epm_ch_rsense = devm_kzalloc(&spi->dev,
			epm_num_channels * sizeof(u32), GFP_KERNEL);
	if (!epm_ch_rsense) {
		dev_err(&spi->dev, "cannot allocate rsense\n");
		return -ENOMEM;
	}

	rc = of_property_read_u32_array(node,
			"qcom,gain", epm_ch_gain, epm_num_channels);
	if (rc) {
		dev_err(&spi->dev, "invalid gain property:%d\n", rc);
		return rc;
	}

	rc = of_property_read_u32_array(node,
			"qcom,rsense", epm_ch_rsense, epm_num_channels);
	if (rc) {
		dev_err(&spi->dev, "invalid rsense property:%d\n", rc);
		return rc;
	}

	rc = of_property_read_u32(node,
			"qcom,channel-type", &channel_mask);
	if (rc) {
		dev_err(&spi->dev, "missing channel mask\n");
		return -ENODEV;
	}

	epm_gpio_num = of_get_named_gpio(spi->dev.of_node,
						"qcom,epm-enable-gpio", 0);
	if (epm_gpio_num < 0) {
		dev_err(&spi->dev, "missing global en gpio num\n");
		return -ENODEV;
	}

	epm_adc = devm_kzalloc(&spi->dev,
			sizeof(struct epm_adc_drv) +
			(epm_num_channels *
			sizeof(struct epm_chan_properties)),
			GFP_KERNEL);
	if (!epm_adc) {
		dev_err(&spi->dev, "Unable to allocate memory\n");
		return -ENOMEM;
	}

	for (i = 0; i < epm_num_channels; i++) {
		epm_adc->epm_psoc_ch_prop[i].resistorvalue =
							epm_ch_rsense[i];
		epm_adc->epm_psoc_ch_prop[i].gain =
							epm_ch_gain[i];
	}

	epm_adc->channel_mask = channel_mask;
	epm_adc->epm_global_en_gpio = epm_gpio_num;
	epm_adc_drv = epm_adc;

	return 0;
}

static int epm_adc_psoc_spi_probe(struct spi_device *spi)
{

	struct epm_adc_drv *epm_adc;
	struct device_node *node = spi->dev.of_node;
	int rc = 0;

	if (node) {
		rc = get_device_tree_data(spi);
		if (rc)
			return rc;
	} else {
		epm_adc = epm_adc_drv;
		epm_adc_drv->epm_spi_client = spi;
		epm_adc_drv->epm_spi_client->bits_per_word =
				EPM_ADC_SPI_BITS_PER_WORD;
		return rc;
	}

	epm_adc = epm_adc_drv;
	epm_adc->misc.name = EPM_ADC_DRIVER_NAME;
	epm_adc->misc.minor = MISC_DYNAMIC_MINOR;

	if (node) {
		epm_adc->misc.fops = &epm_adc_fops;
		if (misc_register(&epm_adc->misc)) {
			pr_err("Unable to register misc device!\n");
			return -EFAULT;
		}
	}

	epm_adc_drv->epm_spi_client = spi;
	epm_adc_drv->epm_spi_client->bits_per_word =
				EPM_ADC_SPI_BITS_PER_WORD;

	epm_adc->hwmon = hwmon_device_register(&spi->dev);
	if (IS_ERR(epm_adc->hwmon)) {
		dev_err(&spi->dev, "hwmon_device_register failed\n");
		return rc;
	}

	mutex_init(&epm_adc->conv_lock);
	return rc;
}

static int epm_adc_psoc_spi_remove(struct spi_device *spi)
{
	epm_adc_drv->epm_spi_client = NULL;
	return 0;
}

static const struct of_device_id epm_adc_psoc_match_table[] = {
	{	.compatible = "cy,epm-adc-cy8c5568lti-114",
	},
	{}
};

static struct spi_driver epm_spi_driver = {
	.probe = epm_adc_psoc_spi_probe,
	.remove = epm_adc_psoc_spi_remove,
	.driver = {
		.name = EPM_ADC_DRIVER_NAME,
		.of_match_table = epm_adc_psoc_match_table,
	},
};

static int __init epm_adc_init(void)
{
	int ret = 0;

	ret = spi_register_driver(&epm_spi_driver);
	if (ret)
		pr_err("%s: spi register failed: rc=%d\n", __func__, ret);

	return ret;
}

static void __exit epm_adc_exit(void)
{
	spi_unregister_driver(&epm_spi_driver);
}

module_init(epm_adc_init);
module_exit(epm_adc_exit);

MODULE_DESCRIPTION("EPM ADC Driver");
MODULE_ALIAS("platform:epm_adc");
MODULE_LICENSE("GPL v2");
+17 −0
Original line number Diff line number Diff line
#ifndef __EPM_ADC_H
#define __EPM_ADC_H

#include <linux/i2c.h>
#include <uapi/linux/epm_adc.h>

struct epm_adc_platform_data {
	struct epm_chan_properties *channel;
	uint32_t num_channels;
	uint32_t num_adc;
	uint32_t chan_per_adc;
	uint32_t chan_per_mux;
	struct i2c_board_info epm_i2c_board_info;
	uint32_t bus_id;
	uint32_t gpio_expander_base_addr;
};
#endif /* __EPM_ADC_H */
Loading