qcom-labibb-regulator.c 25.7 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2020, The Linux Foundation. All rights reserved.

#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>

#define REG_PERPH_TYPE                  0x04

#define QCOM_LAB_TYPE			0x24
#define QCOM_IBB_TYPE			0x20

#define PMI8998_LAB_REG_BASE		0xde00
#define PMI8998_IBB_REG_BASE		0xdc00
#define PMI8998_IBB_LAB_REG_OFFSET	0x200

#define REG_LABIBB_STATUS1		0x08
 #define LABIBB_STATUS1_SC_BIT		BIT(6)
 #define LABIBB_STATUS1_VREG_OK_BIT	BIT(7)

#define REG_LABIBB_INT_SET_TYPE		0x11
#define REG_LABIBB_INT_POLARITY_HIGH	0x12
#define REG_LABIBB_INT_POLARITY_LOW	0x13
#define REG_LABIBB_INT_LATCHED_CLR	0x14
#define REG_LABIBB_INT_EN_SET		0x15
#define REG_LABIBB_INT_EN_CLR		0x16
 #define LABIBB_INT_VREG_OK		BIT(0)
 #define LABIBB_INT_VREG_TYPE_LEVEL	0

#define REG_LABIBB_VOLTAGE		0x41
 #define LABIBB_VOLTAGE_OVERRIDE_EN	BIT(7)
 #define LAB_VOLTAGE_SET_MASK		GENMASK(3, 0)
 #define IBB_VOLTAGE_SET_MASK		GENMASK(5, 0)

#define REG_LABIBB_ENABLE_CTL		0x46
 #define LABIBB_CONTROL_ENABLE		BIT(7)

#define REG_LABIBB_PD_CTL		0x47
 #define LAB_PD_CTL_MASK		GENMASK(1, 0)
 #define IBB_PD_CTL_MASK		(BIT(0) | BIT(7))
 #define LAB_PD_CTL_STRONG_PULL		BIT(0)
 #define IBB_PD_CTL_HALF_STRENGTH	BIT(0)
 #define IBB_PD_CTL_EN			BIT(7)

#define REG_LABIBB_CURRENT_LIMIT	0x4b
 #define LAB_CURRENT_LIMIT_MASK		GENMASK(2, 0)
 #define IBB_CURRENT_LIMIT_MASK		GENMASK(4, 0)
 #define LAB_CURRENT_LIMIT_OVERRIDE_EN	BIT(3)
 #define LABIBB_CURRENT_LIMIT_EN	BIT(7)

#define REG_IBB_PWRUP_PWRDN_CTL_1	0x58
 #define IBB_CTL_1_DISCHARGE_EN		BIT(2)

#define REG_LABIBB_SOFT_START_CTL	0x5f
#define REG_LABIBB_SEC_ACCESS		0xd0
 #define LABIBB_SEC_UNLOCK_CODE		0xa5

#define LAB_ENABLE_CTL_MASK		BIT(7)
#define IBB_ENABLE_CTL_MASK		(BIT(7) | BIT(6))

#define LABIBB_OFF_ON_DELAY		1000
#define LAB_ENABLE_TIME			(LABIBB_OFF_ON_DELAY * 2)
#define IBB_ENABLE_TIME			(LABIBB_OFF_ON_DELAY * 10)
#define LABIBB_POLL_ENABLED_TIME	1000
#define OCP_RECOVERY_INTERVAL_MS	500
#define SC_RECOVERY_INTERVAL_MS		250
#define LABIBB_MAX_OCP_COUNT		4
#define LABIBB_MAX_SC_COUNT		3
#define LABIBB_MAX_FATAL_COUNT		2

struct labibb_current_limits {
	u32				uA_min;
	u32				uA_step;
	u8				ovr_val;
};

struct labibb_regulator {
	struct regulator_desc		desc;
	struct device			*dev;
	struct regmap			*regmap;
	struct regulator_dev		*rdev;
	struct labibb_current_limits	uA_limits;
	struct delayed_work		ocp_recovery_work;
	struct delayed_work		sc_recovery_work;
	u16				base;
	u8				type;
	u8				dischg_sel;
	u8				soft_start_sel;
	int				sc_irq;
	int				sc_count;
	int				ocp_irq;
	int				ocp_irq_count;
	int				fatal_count;
};

struct labibb_regulator_data {
	const char			*name;
	u8				type;
	u16				base;
	const struct regulator_desc	*desc;
};

static int qcom_labibb_ocp_hw_enable(struct regulator_dev *rdev)
{
	struct labibb_regulator *vreg = rdev_get_drvdata(rdev);
	int ret;

	/* Clear irq latch status to avoid spurious event */
	ret = regmap_update_bits(rdev->regmap,
				 vreg->base + REG_LABIBB_INT_LATCHED_CLR,
				 LABIBB_INT_VREG_OK, 1);
	if (ret)
		return ret;

	/* Enable OCP HW interrupt */
	return regmap_update_bits(rdev->regmap,
				  vreg->base + REG_LABIBB_INT_EN_SET,
				  LABIBB_INT_VREG_OK, 1);
}

static int qcom_labibb_ocp_hw_disable(struct regulator_dev *rdev)
{
	struct labibb_regulator *vreg = rdev_get_drvdata(rdev);

	return regmap_update_bits(rdev->regmap,
				  vreg->base + REG_LABIBB_INT_EN_CLR,
				  LABIBB_INT_VREG_OK, 1);
}

/**
 * qcom_labibb_check_ocp_status - Check the Over-Current Protection status
 * @vreg: Main driver structure
 *
 * This function checks the STATUS1 register for the VREG_OK bit: if it is
 * set, then there is no Over-Current event.
 *
 * Returns: Zero if there is no over-current, 1 if in over-current or
 *          negative number for error
 */
static int qcom_labibb_check_ocp_status(struct labibb_regulator *vreg)
{
	u32 cur_status;
	int ret;

	ret = regmap_read(vreg->rdev->regmap, vreg->base + REG_LABIBB_STATUS1,
			  &cur_status);
	if (ret)
		return ret;

	return !(cur_status & LABIBB_STATUS1_VREG_OK_BIT);
}

/**
 * qcom_labibb_ocp_recovery_worker - Handle OCP event
 * @work: OCP work structure
 *
 * This is the worker function to handle the Over Current Protection
 * hardware event; This will check if the hardware is still
 * signaling an over-current condition and will eventually stop
 * the regulator if such condition is still signaled after
 * LABIBB_MAX_OCP_COUNT times.
 *
 * If the driver that is consuming the regulator did not take action
 * for the OCP condition, or the hardware did not stabilize, a cut
 * of the LAB and IBB regulators will be forced (regulators will be
 * disabled).
 *
 * As last, if the writes to shut down the LAB/IBB regulators fail
 * for more than LABIBB_MAX_FATAL_COUNT, then a kernel panic will be
 * triggered, as a last resort to protect the hardware from burning;
 * this, however, is expected to never happen, but this is kept to
 * try to further ensure that we protect the hardware at all costs.
 */
static void qcom_labibb_ocp_recovery_worker(struct work_struct *work)
{
	struct labibb_regulator *vreg;
	const struct regulator_ops *ops;
	int ret;

	vreg = container_of(work, struct labibb_regulator,
			    ocp_recovery_work.work);
	ops = vreg->rdev->desc->ops;

	if (vreg->ocp_irq_count >= LABIBB_MAX_OCP_COUNT) {
		/*
		 * If we tried to disable the regulator multiple times but
		 * we kept failing, there's only one last hope to save our
		 * hardware from the death: raise a kernel bug, reboot and
		 * hope that the bootloader kindly saves us. This, though
		 * is done only as paranoid checking, because failing the
		 * regmap write to disable the vreg is almost impossible,
		 * since we got here after multiple regmap R/W.
		 */
		BUG_ON(vreg->fatal_count > LABIBB_MAX_FATAL_COUNT);
		dev_err(&vreg->rdev->dev, "LABIBB: CRITICAL: Disabling regulator\n");

		/* Disable the regulator immediately to avoid damage */
		ret = ops->disable(vreg->rdev);
		if (ret) {
			vreg->fatal_count++;
			goto reschedule;
		}
		enable_irq(vreg->ocp_irq);
		vreg->fatal_count = 0;
		return;
	}

	ret = qcom_labibb_check_ocp_status(vreg);
	if (ret != 0) {
		vreg->ocp_irq_count++;
		goto reschedule;
	}

	ret = qcom_labibb_ocp_hw_enable(vreg->rdev);
	if (ret) {
		/* We cannot trust it without OCP enabled. */
		dev_err(vreg->dev, "Cannot enable OCP IRQ\n");
		vreg->ocp_irq_count++;
		goto reschedule;
	}

	enable_irq(vreg->ocp_irq);
	/* Everything went fine: reset the OCP count! */
	vreg->ocp_irq_count = 0;
	return;

reschedule:
	mod_delayed_work(system_wq, &vreg->ocp_recovery_work,
			 msecs_to_jiffies(OCP_RECOVERY_INTERVAL_MS));
}

/**
 * qcom_labibb_ocp_isr - Interrupt routine for OverCurrent Protection
 * @irq:  Interrupt number
 * @chip: Main driver structure
 *
 * Over Current Protection (OCP) will signal to the client driver
 * that an over-current event has happened and then will schedule
 * a recovery worker.
 *
 * Disabling and eventually re-enabling the regulator is expected
 * to be done by the driver, as some hardware may be triggering an
 * over-current condition only at first initialization or it may
 * be expected only for a very brief amount of time, after which
 * the attached hardware may be expected to stabilize its current
 * draw.
 *
 * Returns: IRQ_HANDLED for success or IRQ_NONE for failure.
 */
static irqreturn_t qcom_labibb_ocp_isr(int irq, void *chip)
{
	struct labibb_regulator *vreg = chip;
	const struct regulator_ops *ops = vreg->rdev->desc->ops;
	int ret;

	/* If the regulator is not enabled, this is a fake event */
	if (!ops->is_enabled(vreg->rdev))
		return IRQ_HANDLED;

	/* If we tried to recover for too many times it's not getting better */
	if (vreg->ocp_irq_count > LABIBB_MAX_OCP_COUNT)
		return IRQ_NONE;

	/*
	 * If we (unlikely) can't read this register, to prevent hardware
	 * damage at all costs, we assume that the overcurrent event was
	 * real; Moreover, if the status register is not signaling OCP,
	 * it was a spurious event, so it's all ok.
	 */
	ret = qcom_labibb_check_ocp_status(vreg);
	if (ret == 0) {
		vreg->ocp_irq_count = 0;
		goto end;
	}
	vreg->ocp_irq_count++;

	/*
	 * Disable the interrupt temporarily, or it will fire continuously;
	 * we will re-enable it in the recovery worker function.
	 */
	disable_irq_nosync(irq);

	/* Warn the user for overcurrent */
	dev_warn(vreg->dev, "Over-Current interrupt fired!\n");

	/* Disable the interrupt to avoid hogging */
	ret = qcom_labibb_ocp_hw_disable(vreg->rdev);
	if (ret)
		goto end;

	/* Signal overcurrent event to drivers */
	regulator_notifier_call_chain(vreg->rdev,
				      REGULATOR_EVENT_OVER_CURRENT, NULL);

end:
	/* Schedule the recovery work */
	schedule_delayed_work(&vreg->ocp_recovery_work,
			      msecs_to_jiffies(OCP_RECOVERY_INTERVAL_MS));
	if (ret)
		return IRQ_NONE;

	return IRQ_HANDLED;
}

static int qcom_labibb_set_ocp(struct regulator_dev *rdev, int lim,
			       int severity, bool enable)
{
	struct labibb_regulator *vreg = rdev_get_drvdata(rdev);
	char *ocp_irq_name;
	u32 irq_flags = IRQF_ONESHOT;
	int irq_trig_low, ret;

	/*
	 * labibb supports only protection - and does not support setting
	 * limit. Furthermore, we don't support disabling protection.
	 */
	if (lim || severity != REGULATOR_SEVERITY_PROT || !enable)
		return -EINVAL;

	/* If there is no OCP interrupt, there's nothing to set */
	if (vreg->ocp_irq <= 0)
		return -EINVAL;

	ocp_irq_name = devm_kasprintf(vreg->dev, GFP_KERNEL, "%s-over-current",
				      vreg->desc.name);
	if (!ocp_irq_name)
		return -ENOMEM;

	/* IRQ polarities - LAB: trigger-low, IBB: trigger-high */
	switch (vreg->type) {
	case QCOM_LAB_TYPE:
		irq_flags |= IRQF_TRIGGER_LOW;
		irq_trig_low = 1;
		break;
	case QCOM_IBB_TYPE:
		irq_flags |= IRQF_TRIGGER_HIGH;
		irq_trig_low = 0;
		break;
	default:
		return -EINVAL;
	}

	/* Activate OCP HW level interrupt */
	ret = regmap_update_bits(rdev->regmap,
				 vreg->base + REG_LABIBB_INT_SET_TYPE,
				 LABIBB_INT_VREG_OK,
				 LABIBB_INT_VREG_TYPE_LEVEL);
	if (ret)
		return ret;

	/* Set OCP interrupt polarity */
	ret = regmap_update_bits(rdev->regmap,
				 vreg->base + REG_LABIBB_INT_POLARITY_HIGH,
				 LABIBB_INT_VREG_OK, !irq_trig_low);
	if (ret)
		return ret;
	ret = regmap_update_bits(rdev->regmap,
				 vreg->base + REG_LABIBB_INT_POLARITY_LOW,
				 LABIBB_INT_VREG_OK, irq_trig_low);
	if (ret)
		return ret;

	ret = qcom_labibb_ocp_hw_enable(rdev);
	if (ret)
		return ret;

	return devm_request_threaded_irq(vreg->dev, vreg->ocp_irq, NULL,
					 qcom_labibb_ocp_isr, irq_flags,
					 ocp_irq_name, vreg);
}

/**
 * qcom_labibb_check_sc_status - Check the Short Circuit Protection status
 * @vreg: Main driver structure
 *
 * This function checks the STATUS1 register on both LAB and IBB regulators
 * for the ShortCircuit bit: if it is set on *any* of them, then we have
 * experienced a short-circuit event.
 *
 * Returns: Zero if there is no short-circuit, 1 if in short-circuit or
 *          negative number for error
 */
static int qcom_labibb_check_sc_status(struct labibb_regulator *vreg)
{
	u32 ibb_status, ibb_reg, lab_status, lab_reg;
	int ret;

	/* We have to work on both regulators due to PBS... */
	lab_reg = ibb_reg = vreg->base + REG_LABIBB_STATUS1;
	if (vreg->type == QCOM_LAB_TYPE)
		ibb_reg -= PMI8998_IBB_LAB_REG_OFFSET;
	else
		lab_reg += PMI8998_IBB_LAB_REG_OFFSET;

	ret = regmap_read(vreg->rdev->regmap, lab_reg, &lab_status);
	if (ret)
		return ret;
	ret = regmap_read(vreg->rdev->regmap, ibb_reg, &ibb_status);
	if (ret)
		return ret;

	return !!(lab_status & LABIBB_STATUS1_SC_BIT) ||
	       !!(ibb_status & LABIBB_STATUS1_SC_BIT);
}

/**
 * qcom_labibb_sc_recovery_worker - Handle Short Circuit event
 * @work: SC work structure
 *
 * This is the worker function to handle the Short Circuit Protection
 * hardware event; This will check if the hardware is still
 * signaling a short-circuit condition and will eventually never
 * re-enable the regulator if such condition is still signaled after
 * LABIBB_MAX_SC_COUNT times.
 *
 * If the driver that is consuming the regulator did not take action
 * for the SC condition, or the hardware did not stabilize, this
 * worker will stop rescheduling, leaving the regulators disabled
 * as already done by the Portable Batch System (PBS).
 *
 * Returns: IRQ_HANDLED for success or IRQ_NONE for failure.
 */
static void qcom_labibb_sc_recovery_worker(struct work_struct *work)
{
	struct labibb_regulator *vreg;
	const struct regulator_ops *ops;
	u32 lab_reg, ibb_reg, lab_val, ibb_val, val;
	bool pbs_cut = false;
	int i, sc, ret;

	vreg = container_of(work, struct labibb_regulator,
			    sc_recovery_work.work);
	ops = vreg->rdev->desc->ops;

	/*
	 * If we tried to check the regulator status multiple times but we
	 * kept failing, then just bail out, as the Portable Batch System
	 * (PBS) will disable the vregs for us, preventing hardware damage.
	 */
	if (vreg->fatal_count > LABIBB_MAX_FATAL_COUNT)
		return;

	/* Too many short-circuit events. Throw in the towel. */
	if (vreg->sc_count > LABIBB_MAX_SC_COUNT)
		return;

	/*
	 * The Portable Batch System (PBS) automatically disables LAB
	 * and IBB when a short-circuit event is detected, so we have to
	 * check and work on both of them at the same time.
	 */
	lab_reg = ibb_reg = vreg->base + REG_LABIBB_ENABLE_CTL;
	if (vreg->type == QCOM_LAB_TYPE)
		ibb_reg -= PMI8998_IBB_LAB_REG_OFFSET;
	else
		lab_reg += PMI8998_IBB_LAB_REG_OFFSET;

	sc = qcom_labibb_check_sc_status(vreg);
	if (sc)
		goto reschedule;

	for (i = 0; i < LABIBB_MAX_SC_COUNT; i++) {
		ret = regmap_read(vreg->regmap, lab_reg, &lab_val);
		if (ret) {
			vreg->fatal_count++;
			goto reschedule;
		}

		ret = regmap_read(vreg->regmap, ibb_reg, &ibb_val);
		if (ret) {
			vreg->fatal_count++;
			goto reschedule;
		}
		val = lab_val & ibb_val;

		if (!(val & LABIBB_CONTROL_ENABLE)) {
			pbs_cut = true;
			break;
		}
		usleep_range(5000, 6000);
	}
	if (pbs_cut)
		goto reschedule;


	/*
	 * If we have reached this point, we either have successfully
	 * recovered from the SC condition or we had a spurious SC IRQ,
	 * which means that we can re-enable the regulators, if they
	 * have ever been disabled by the PBS.
	 */
	ret = ops->enable(vreg->rdev);
	if (ret)
		goto reschedule;

	/* Everything went fine: reset the OCP count! */
	vreg->sc_count = 0;
	enable_irq(vreg->sc_irq);
	return;

reschedule:
	/*
	 * Now that we have done basic handling of the short-circuit,
	 * reschedule this worker in the regular system workqueue, as
	 * taking action is not truly urgent anymore.
	 */
	vreg->sc_count++;
	mod_delayed_work(system_wq, &vreg->sc_recovery_work,
			 msecs_to_jiffies(SC_RECOVERY_INTERVAL_MS));
}

/**
 * qcom_labibb_sc_isr - Interrupt routine for Short Circuit Protection
 * @irq:  Interrupt number
 * @chip: Main driver structure
 *
 * Short Circuit Protection (SCP) will signal to the client driver
 * that a regulation-out event has happened and then will schedule
 * a recovery worker.
 *
 * The LAB and IBB regulators will be automatically disabled by the
 * Portable Batch System (PBS) and they will be enabled again by
 * the worker function if the hardware stops signaling the short
 * circuit event.
 *
 * Returns: IRQ_HANDLED for success or IRQ_NONE for failure.
 */
static irqreturn_t qcom_labibb_sc_isr(int irq, void *chip)
{
	struct labibb_regulator *vreg = chip;

	if (vreg->sc_count > LABIBB_MAX_SC_COUNT)
		return IRQ_NONE;

	/* Warn the user for short circuit */
	dev_warn(vreg->dev, "Short-Circuit interrupt fired!\n");

	/*
	 * Disable the interrupt temporarily, or it will fire continuously;
	 * we will re-enable it in the recovery worker function.
	 */
	disable_irq_nosync(irq);

	/* Signal out of regulation event to drivers */
	regulator_notifier_call_chain(vreg->rdev,
				      REGULATOR_EVENT_REGULATION_OUT, NULL);

	/* Schedule the short-circuit handling as high-priority work */
	mod_delayed_work(system_highpri_wq, &vreg->sc_recovery_work,
			 msecs_to_jiffies(SC_RECOVERY_INTERVAL_MS));
	return IRQ_HANDLED;
}


static int qcom_labibb_set_current_limit(struct regulator_dev *rdev,
					 int min_uA, int max_uA)
{
	struct labibb_regulator *vreg = rdev_get_drvdata(rdev);
	struct regulator_desc *desc = &vreg->desc;
	struct labibb_current_limits *lim = &vreg->uA_limits;
	u32 mask, val;
	int i, ret, sel = -1;

	if (min_uA < lim->uA_min || max_uA < lim->uA_min)
		return -EINVAL;

	for (i = 0; i < desc->n_current_limits; i++) {
		int uA_limit = (lim->uA_step * i) + lim->uA_min;

		if (max_uA >= uA_limit && min_uA <= uA_limit)
			sel = i;
	}
	if (sel < 0)
		return -EINVAL;

	/* Current limit setting needs secure access */
	ret = regmap_write(vreg->regmap, vreg->base + REG_LABIBB_SEC_ACCESS,
			   LABIBB_SEC_UNLOCK_CODE);
	if (ret)
		return ret;

	mask = desc->csel_mask | lim->ovr_val;
	mask |= LABIBB_CURRENT_LIMIT_EN;
	val = (u32)sel | lim->ovr_val;
	val |= LABIBB_CURRENT_LIMIT_EN;

	return regmap_update_bits(vreg->regmap, desc->csel_reg, mask, val);
}

static int qcom_labibb_get_current_limit(struct regulator_dev *rdev)
{
	struct labibb_regulator *vreg = rdev_get_drvdata(rdev);
	struct regulator_desc *desc = &vreg->desc;
	struct labibb_current_limits *lim = &vreg->uA_limits;
	unsigned int cur_step;
	int ret;

	ret = regmap_read(vreg->regmap, desc->csel_reg, &cur_step);
	if (ret)
		return ret;
	cur_step &= desc->csel_mask;

	return (cur_step * lim->uA_step) + lim->uA_min;
}

static int qcom_labibb_set_soft_start(struct regulator_dev *rdev)
{
	struct labibb_regulator *vreg = rdev_get_drvdata(rdev);
	u32 val = 0;

	if (vreg->type == QCOM_IBB_TYPE)
		val = vreg->dischg_sel;
	else
		val = vreg->soft_start_sel;

	return regmap_write(rdev->regmap, rdev->desc->soft_start_reg, val);
}

static int qcom_labibb_get_table_sel(const int *table, int sz, u32 value)
{
	int i;

	for (i = 0; i < sz; i++)
		if (table[i] == value)
			return i;
	return -EINVAL;
}

/* IBB discharge resistor values in KOhms */
static const int dischg_resistor_values[] = { 300, 64, 32, 16 };

/* Soft start time in microseconds */
static const int soft_start_values[] = { 200, 400, 600, 800 };

static int qcom_labibb_of_parse_cb(struct device_node *np,
				   const struct regulator_desc *desc,
				   struct regulator_config *config)
{
	struct labibb_regulator *vreg = config->driver_data;
	u32 dischg_kohms, soft_start_time;
	int ret;

	ret = of_property_read_u32(np, "qcom,discharge-resistor-kohms",
				       &dischg_kohms);
	if (ret)
		dischg_kohms = 300;

	ret = qcom_labibb_get_table_sel(dischg_resistor_values,
					ARRAY_SIZE(dischg_resistor_values),
					dischg_kohms);
	if (ret < 0)
		return ret;
	vreg->dischg_sel = (u8)ret;

	ret = of_property_read_u32(np, "qcom,soft-start-us",
				   &soft_start_time);
	if (ret)
		soft_start_time = 200;

	ret = qcom_labibb_get_table_sel(soft_start_values,
					ARRAY_SIZE(soft_start_values),
					soft_start_time);
	if (ret < 0)
		return ret;
	vreg->soft_start_sel = (u8)ret;

	return 0;
}

static const struct regulator_ops qcom_labibb_ops = {
	.enable			= regulator_enable_regmap,
	.disable		= regulator_disable_regmap,
	.is_enabled		= regulator_is_enabled_regmap,
	.set_voltage_sel	= regulator_set_voltage_sel_regmap,
	.get_voltage_sel	= regulator_get_voltage_sel_regmap,
	.list_voltage		= regulator_list_voltage_linear,
	.map_voltage		= regulator_map_voltage_linear,
	.set_active_discharge	= regulator_set_active_discharge_regmap,
	.set_pull_down		= regulator_set_pull_down_regmap,
	.set_current_limit	= qcom_labibb_set_current_limit,
	.get_current_limit	= qcom_labibb_get_current_limit,
	.set_soft_start		= qcom_labibb_set_soft_start,
	.set_over_current_protection = qcom_labibb_set_ocp,
};

static const struct regulator_desc pmi8998_lab_desc = {
	.enable_mask		= LAB_ENABLE_CTL_MASK,
	.enable_reg		= (PMI8998_LAB_REG_BASE + REG_LABIBB_ENABLE_CTL),
	.enable_val		= LABIBB_CONTROL_ENABLE,
	.enable_time		= LAB_ENABLE_TIME,
	.poll_enabled_time	= LABIBB_POLL_ENABLED_TIME,
	.soft_start_reg		= (PMI8998_LAB_REG_BASE + REG_LABIBB_SOFT_START_CTL),
	.pull_down_reg		= (PMI8998_LAB_REG_BASE + REG_LABIBB_PD_CTL),
	.pull_down_mask		= LAB_PD_CTL_MASK,
	.pull_down_val_on	= LAB_PD_CTL_STRONG_PULL,
	.vsel_reg		= (PMI8998_LAB_REG_BASE + REG_LABIBB_VOLTAGE),
	.vsel_mask		= LAB_VOLTAGE_SET_MASK,
	.apply_reg		= (PMI8998_LAB_REG_BASE + REG_LABIBB_VOLTAGE),
	.apply_bit		= LABIBB_VOLTAGE_OVERRIDE_EN,
	.csel_reg		= (PMI8998_LAB_REG_BASE + REG_LABIBB_CURRENT_LIMIT),
	.csel_mask		= LAB_CURRENT_LIMIT_MASK,
	.n_current_limits	= 8,
	.off_on_delay		= LABIBB_OFF_ON_DELAY,
	.owner			= THIS_MODULE,
	.type			= REGULATOR_VOLTAGE,
	.min_uV			= 4600000,
	.uV_step		= 100000,
	.n_voltages		= 16,
	.ops			= &qcom_labibb_ops,
	.of_parse_cb		= qcom_labibb_of_parse_cb,
};

static const struct regulator_desc pmi8998_ibb_desc = {
	.enable_mask		= IBB_ENABLE_CTL_MASK,
	.enable_reg		= (PMI8998_IBB_REG_BASE + REG_LABIBB_ENABLE_CTL),
	.enable_val		= LABIBB_CONTROL_ENABLE,
	.enable_time		= IBB_ENABLE_TIME,
	.poll_enabled_time	= LABIBB_POLL_ENABLED_TIME,
	.soft_start_reg		= (PMI8998_IBB_REG_BASE + REG_LABIBB_SOFT_START_CTL),
	.active_discharge_off	= 0,
	.active_discharge_on	= IBB_CTL_1_DISCHARGE_EN,
	.active_discharge_mask	= IBB_CTL_1_DISCHARGE_EN,
	.active_discharge_reg	= (PMI8998_IBB_REG_BASE + REG_IBB_PWRUP_PWRDN_CTL_1),
	.pull_down_reg		= (PMI8998_IBB_REG_BASE + REG_LABIBB_PD_CTL),
	.pull_down_mask		= IBB_PD_CTL_MASK,
	.pull_down_val_on	= IBB_PD_CTL_HALF_STRENGTH | IBB_PD_CTL_EN,
	.vsel_reg		= (PMI8998_IBB_REG_BASE + REG_LABIBB_VOLTAGE),
	.vsel_mask		= IBB_VOLTAGE_SET_MASK,
	.apply_reg		= (PMI8998_IBB_REG_BASE + REG_LABIBB_VOLTAGE),
	.apply_bit		= LABIBB_VOLTAGE_OVERRIDE_EN,
	.csel_reg		= (PMI8998_IBB_REG_BASE + REG_LABIBB_CURRENT_LIMIT),
	.csel_mask		= IBB_CURRENT_LIMIT_MASK,
	.n_current_limits	= 32,
	.off_on_delay		= LABIBB_OFF_ON_DELAY,
	.owner			= THIS_MODULE,
	.type			= REGULATOR_VOLTAGE,
	.min_uV			= 1400000,
	.uV_step		= 100000,
	.n_voltages		= 64,
	.ops			= &qcom_labibb_ops,
	.of_parse_cb		= qcom_labibb_of_parse_cb,
};

static const struct labibb_regulator_data pmi8998_labibb_data[] = {
	{"lab", QCOM_LAB_TYPE, PMI8998_LAB_REG_BASE, &pmi8998_lab_desc},
	{"ibb", QCOM_IBB_TYPE, PMI8998_IBB_REG_BASE, &pmi8998_ibb_desc},
	{ },
};

static const struct of_device_id qcom_labibb_match[] = {
	{ .compatible = "qcom,pmi8998-lab-ibb", .data = &pmi8998_labibb_data},
	{ },
};
MODULE_DEVICE_TABLE(of, qcom_labibb_match);

static int qcom_labibb_regulator_probe(struct platform_device *pdev)
{
	struct labibb_regulator *vreg;
	struct device *dev = &pdev->dev;
	struct regulator_config cfg = {};
	struct device_node *reg_node;
	const struct of_device_id *match;
	const struct labibb_regulator_data *reg_data;
	struct regmap *reg_regmap;
	unsigned int type;
	int ret;

	reg_regmap = dev_get_regmap(pdev->dev.parent, NULL);
	if (!reg_regmap) {
		dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
		return -ENODEV;
	}

	match = of_match_device(qcom_labibb_match, &pdev->dev);
	if (!match)
		return -ENODEV;

	for (reg_data = match->data; reg_data->name; reg_data++) {
		char *sc_irq_name;
		int irq = 0;

		/* Validate if the type of regulator is indeed
		 * what's mentioned in DT.
		 */
		ret = regmap_read(reg_regmap, reg_data->base + REG_PERPH_TYPE,
				  &type);
		if (ret < 0) {
			dev_err(dev,
				"Peripheral type read failed ret=%d\n",
				ret);
			return -EINVAL;
		}

		if (WARN_ON((type != QCOM_LAB_TYPE) && (type != QCOM_IBB_TYPE)) ||
		    WARN_ON(type != reg_data->type))
			return -EINVAL;

		vreg  = devm_kzalloc(&pdev->dev, sizeof(*vreg),
					   GFP_KERNEL);
		if (!vreg)
			return -ENOMEM;

		sc_irq_name = devm_kasprintf(dev, GFP_KERNEL,
					     "%s-short-circuit",
					     reg_data->name);
		if (!sc_irq_name)
			return -ENOMEM;

		reg_node = of_get_child_by_name(pdev->dev.of_node,
						reg_data->name);
		if (!reg_node)
			return -EINVAL;

		/* The Short Circuit interrupt is critical */
		irq = of_irq_get_byname(reg_node, "sc-err");
		if (irq <= 0) {
			if (irq == 0)
				irq = -EINVAL;

			return dev_err_probe(vreg->dev, irq,
					     "Short-circuit irq not found.\n");
		}
		vreg->sc_irq = irq;

		/* OverCurrent Protection IRQ is optional */
		irq = of_irq_get_byname(reg_node, "ocp");
		vreg->ocp_irq = irq;
		vreg->ocp_irq_count = 0;
		of_node_put(reg_node);

		vreg->regmap = reg_regmap;
		vreg->dev = dev;
		vreg->base = reg_data->base;
		vreg->type = reg_data->type;
		INIT_DELAYED_WORK(&vreg->sc_recovery_work,
				  qcom_labibb_sc_recovery_worker);

		if (vreg->ocp_irq > 0)
			INIT_DELAYED_WORK(&vreg->ocp_recovery_work,
					  qcom_labibb_ocp_recovery_worker);

		switch (vreg->type) {
		case QCOM_LAB_TYPE:
			/* LAB Limits: 200-1600mA */
			vreg->uA_limits.uA_min  = 200000;
			vreg->uA_limits.uA_step = 200000;
			vreg->uA_limits.ovr_val = LAB_CURRENT_LIMIT_OVERRIDE_EN;
			break;
		case QCOM_IBB_TYPE:
			/* IBB Limits: 0-1550mA */
			vreg->uA_limits.uA_min  = 0;
			vreg->uA_limits.uA_step = 50000;
			vreg->uA_limits.ovr_val = 0; /* No override bit */
			break;
		default:
			return -EINVAL;
		}

		memcpy(&vreg->desc, reg_data->desc, sizeof(vreg->desc));
		vreg->desc.of_match = reg_data->name;
		vreg->desc.name = reg_data->name;

		cfg.dev = vreg->dev;
		cfg.driver_data = vreg;
		cfg.regmap = vreg->regmap;

		vreg->rdev = devm_regulator_register(vreg->dev, &vreg->desc,
							&cfg);

		if (IS_ERR(vreg->rdev)) {
			dev_err(dev, "qcom_labibb: error registering %s : %d\n",
					reg_data->name, ret);
			return PTR_ERR(vreg->rdev);
		}

		ret = devm_request_threaded_irq(vreg->dev, vreg->sc_irq, NULL,
						qcom_labibb_sc_isr,
						IRQF_ONESHOT |
						IRQF_TRIGGER_RISING,
						sc_irq_name, vreg);
		if (ret)
			return ret;
	}

	return 0;
}

static struct platform_driver qcom_labibb_regulator_driver = {
	.driver	= {
		.name = "qcom-lab-ibb-regulator",
		.of_match_table	= qcom_labibb_match,
	},
	.probe = qcom_labibb_regulator_probe,
};
module_platform_driver(qcom_labibb_regulator_driver);

MODULE_DESCRIPTION("Qualcomm labibb driver");
MODULE_AUTHOR("Nisha Kumari <nishakumari@codeaurora.org>");
MODULE_AUTHOR("Sumit Semwal <sumit.semwal@linaro.org>");
MODULE_LICENSE("GPL v2");