Commit da77e5ab authored by Andrey Filippov's avatar Andrey Filippov
Browse files

continue work on elphel393-pwr.c

parent a56a0878
Loading
Loading
Loading
Loading
+209 −0
Original line number Original line Diff line number Diff line
@@ -22,10 +22,219 @@
#include <linux/init.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/string.h>
#include <linux/of.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/i2c/ltc3589.h>

#define DRIVER_DESCRIPTION	"Elphel 10393 power supply control"
#define DRIVER_VERSION		"1.00"

#define SYSFS_PERMISSIONS         0644 /* default permissions for sysfs files */
#define SYSFS_READONLY            0444
#define SYSFS_WRITEONLY           0222


#define GPIO_CHIP1_ADDR 0x20
#define GPIO_CHIP2_ADDR 0x21
#define LTC3589_ADDR    0x34



struct elphel393_pwr_data_t {
	int chip_i2c_addr[3];
	int simulate; /* do not perform actual i2c writes */
	struct mutex lock;
};

struct pwr_gpio_t {
	const char * label;
	int pin;
};


static struct pwr_gpio_t pwr_gpio [16]={
/* 0x20: */
		{"PWR_MGB1",    0}, /* 1.8V margining magnitude (0 - 5%, 1 - 10%, float - 15%) */
		{"PWR_MG1",     1}, /* 1.8V margining enable 0 - negative margining, 1 - positive margining, float - no margining */
		{"PWR_MGB0",    2}, /* 1.0V margining magnitude (0 - 5%, 1 - 10%, float - 15%) */
		{"PWR_MG0",     3},  /* 1.0V margining enable 0 - negative margining, 1 - positive margining, float - no margining */
		{"PWR_FQ0",     4}, /*   float - nominal frequency (should float for SS), 0 - 0.67 nominal frequency, 1 - 1.5 nominal frequency */ 
		{"PWR_SS",      5}, /*  Spread spectrum, 0 or float - spread spectrum disabled  */
		{"SENSPWREN0",  6}, /*  1 - enable 3.3 power to sensor connectors J6 and J7 (0 or float - disable) */
		{"SENSPWREN1",  7}, /*  1 - enable 3.3 power to sensor connectors J8 and J9 (0 or float - disable) */
/* 0x21: */
		{"NSHUTDOWN",   8}, /*  (pulled up). 0 - shutdown, 1 normal */
		{"DIS_POR",     9}, /* (pulled down). 0 - normal, 1 - disable POR generation on PGOOD deassertion (needed whil changing voltages) */
		{ NULL,        10}, /* Not connected */
		{ NULL,        11}, /* Not connected */
		{ NULL,        12}, /* Not connected */
		{ NULL,        13}, /* Not connected */
		{"MGTAVTTGOOD",14}, /*  (input) 1.2V linear regulator status (generated from 1.8V) */
		{"PGOOD18",    15} /*  (input). Combines other voltages, can be monitored when DIS_POR is activated */
};

static ssize_t simulate_show (struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t simulate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);


/* root directory */
//static DEVICE_ATTR(outputs,   SYSFS_PERMISSIONS & SYSFS_READONLY,  output_description_show, NULL);
//static DEVICE_ATTR(status,    SYSFS_PERMISSIONS & SYSFS_READONLY,  status_show, NULL);
static DEVICE_ATTR(simulate,    SYSFS_PERMISSIONS,                     simulate_show,       simulate_store);

static struct attribute *root_dev_attrs[] = {
		&dev_attr_simulate.attr,
	    NULL
};
static const struct attribute_group dev_attr_root_group = {
	.attrs = root_dev_attrs,
	.name  = NULL,
};

static ssize_t simulate_show (struct device *dev, struct device_attribute *attr, char *buf)
{
	struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
    return sprintf(buf, "%d\n",clientdata->simulate);
}
static ssize_t simulate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
    sscanf(buf, "%du", &clientdata->simulate);
    return count;
}

static int elphel393_pwr_sysfs_register(struct platform_device *pdev)
{
	int retval=0;
	struct device *dev = &pdev->dev;
	if (&dev->kobj) {
		if (((retval = sysfs_create_group(&dev->kobj, &dev_attr_root_group)))<0) return retval;
	}
	return retval;
}
static void elphel393_pwr_init_of(struct platform_device *pdev)
{
	const __be32 * config_data;
//	struct device *dev = &pdev->dev;
	struct device_node *node = pdev->dev.of_node;
	struct elphel393_pwr_data_t *clientdata = platform_get_drvdata(pdev);
	int len,i;
	if (node) {
		config_data = of_get_property(node, "elphel393_pwr,i2c_chips", &len);
		if (config_data){
			len /= sizeof(*config_data);
			dev_info(&pdev->dev,"Found %d items in 'elphel393_pwr,i2c_chips' in the Device Tree\n",len);
			if (len!= ARRAY_SIZE(clientdata->chip_i2c_addr)){
				dev_err(&pdev->dev,"Got %d items in 'elphel393_pwr,i2c_chips', expected %d\n",len,ARRAY_SIZE(clientdata->chip_i2c_addr));
				return;
			}
			for (i=0;i<len;i++)	clientdata->chip_i2c_addr[i]=be32_to_cpup(&config_data[i]);
		}
	}	
}

static int device_by_i2c_addr_match(struct device *dev, void *data)
{
	struct i2c_client *client = to_i2c_client(dev);
	int *addr = (int *)data;
	dev_info(dev,"addr_given=0x%02x, addr found=0x%02x\n",addr[0],(int) client->addr);
	return i2c_verify_client(dev) && (client->addr==addr[0]);
}

static struct device * find_device_by_i2c_addr(int address)
{
	return bus_find_device(&i2c_bus_type, NULL, &address, device_by_i2c_addr_match);
}

static int i2c_addr_gpiochip_match(struct gpio_chip *chip, void *data)
{
	struct i2c_client *client = to_i2c_client(chip->dev);
	int *addr = (int *)data;
	dev_info(chip->dev,"addr_given=0x%02x, addr found=0x%02x\n",addr[0],(int) client->addr);
	return i2c_verify_client(chip->dev) && (client->addr==addr[0]);
}

static int elphel393_pwr_probe(struct platform_device *pdev)
{
	struct gpio_chip *chip;
	struct device * ltc3489_dev;
	int i,rc;
	int base[2];
	struct i2c_client *ltc3589_client;
	struct elphel393_pwr_data_t *clientdata = NULL;

	dev_info(&pdev->dev,"+++ Probing elphel393-pwr +++");
	clientdata = devm_kzalloc(&pdev->dev, sizeof(*clientdata), GFP_KERNEL);
	clientdata->chip_i2c_addr[0]=0x20;
	clientdata->chip_i2c_addr[1]=0x21;
	clientdata->chip_i2c_addr[2]=0x34;
	platform_set_drvdata(pdev, clientdata);

	elphel393_pwr_sysfs_register(pdev);
	elphel393_pwr_init_of(pdev);

	mutex_init(&clientdata->lock);
	
	/* locate GPIO chips by i2c address */
    for (i=0;i<2;i++){
    	chip = gpiochip_find(&clientdata->chip_i2c_addr[i], i2c_addr_gpiochip_match);
    	base[i]=chip->base;
    	dev_info(&pdev->dev,"Found gpio_chip with i2c_addr=0x%02x, label=%s, base=0x%x\n",clientdata->chip_i2c_addr[i],chip->label,base[i]);
    }
    for (i=0;i<ARRAY_SIZE(pwr_gpio);i++) if (pwr_gpio[i].label){
    	pwr_gpio[i].pin=base[i>>3]+(i & 7);
    	rc=gpio_request(pwr_gpio[i].pin, pwr_gpio[i].label);
    	if (rc<0){
    		dev_err(&pdev->dev," Failed to get GPIO[%d] with label %s\n",pwr_gpio[i].pin,pwr_gpio[i].label);
    	} else {
    		dev_info(&pdev->dev,"Confirmed request GPIO[%d] with label %s\n",pwr_gpio[i].pin,pwr_gpio[i].label);
    	}
    }
    /* find ltc3589 */
    ltc3489_dev=find_device_by_i2c_addr(LTC3589_ADDR);
    if (!ltc3489_dev){
		dev_err(&pdev->dev," Failed to find LTC3489 with i2c address 0x%02x\n",LTC3589_ADDR);
		return -EIO;
    }
    ltc3589_client = to_i2c_client(ltc3489_dev);

	dev_info(&pdev->dev,"Located %s with i2c address 0x%02x\n",ltc3589_client->name,LTC3589_ADDR);
	dev_info(&pdev->dev,"LTC3589 status= 0x%02x\n",read_field_ltc3589(ltc3589_client, LTC3589_AWE_PGSTAT));
	
	return 0;
}	


static int elphel393_pwr_remove(struct platform_device *pdev)
{
	dev_info(&pdev->dev,"+++ Removing elphel393-pwr +++");
	return 0;
}



static struct of_device_id elphel393_pwr_of_match[] = {
	{ .compatible = "elphel,elphel393-pwr-1.00", },
	{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, elphel393_pwr_of_match);

static struct platform_driver elphel393_pwr = {
	.probe   = elphel393_pwr_probe,
	.remove  = elphel393_pwr_remove,
	.driver  = {
		.name  = "elphel393-pwr",
		.owner = THIS_MODULE,
		.of_match_table = elphel393_pwr_of_match,
		.pm = NULL, /* power management */
	},
};

module_platform_driver(elphel393_pwr);


MODULE_AUTHOR("Andrey Filippov  <andrey@elphel.com>");
MODULE_AUTHOR("Andrey Filippov  <andrey@elphel.com>");
MODULE_DESCRIPTION("Elphel 10393 power supply control");
MODULE_DESCRIPTION("Elphel 10393 power supply control");