diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h
index bf70255a6a5ea364..8c8545b074e8ae13 100644
--- a/include/libcamera/camera.h
+++ b/include/libcamera/camera.h
@@ -55,20 +55,28 @@ public:
 	int stop();
 
 private:
+	enum State {
+		Disconnected,
+		Free,
+		Acquired,
+		Running,
+	};
+
 	Camera(PipelineHandler *pipe, const std::string &name);
 	~Camera();
 
+	bool stateIs(State state) const;
+	bool stateIsAtleast(State state) const;
+
 	friend class PipelineHandler;
 	void disconnect();
-	int exclusiveAccess();
 
 	std::shared_ptr<PipelineHandler> pipe_;
 	std::string name_;
 	std::vector<Stream *> streams_;
 	std::vector<Stream *> activeStreams_;
 
-	bool acquired_;
-	bool disconnected_;
+	State state_;
 };
 
 } /* namespace libcamera */
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
index d4258fe3c7551af3..c50b14bbd904fc1c 100644
--- a/src/libcamera/camera.cpp
+++ b/src/libcamera/camera.cpp
@@ -116,17 +116,47 @@ const std::string &Camera::name() const
  */
 
 Camera::Camera(PipelineHandler *pipe, const std::string &name)
-	: pipe_(pipe->shared_from_this()), name_(name), acquired_(false),
-	  disconnected_(false)
+	: pipe_(pipe->shared_from_this()), name_(name), state_(Free)
 {
 }
 
 Camera::~Camera()
 {
-	if (acquired_)
+	if (state_ > Free)
 		LOG(Camera, Error) << "Removing camera while still in use";
 }
 
+static const std::string stateNames[] = {
+	"Disconnected",
+	"Free",
+	"Acquired",
+	"Running",
+};
+
+bool Camera::stateIs(State state) const
+{
+	if (state_ == state)
+		return true;
+
+	LOG(Camera, Error) << "Camera in " << stateNames[state_]
+			   << " state trying operation requiering "
+			   << stateNames[state] << " state";
+
+	return false;
+}
+
+bool Camera::stateIsAtleast(State state) const
+{
+	if (state_ >= state)
+		return true;
+
+	LOG(Camera, Error) << "Camera in " << stateNames[state_]
+			   << " state trying operation requiering at least "
+			   << stateNames[state] << " state";
+
+	return false;
+}
+
 /**
  * \brief Notify camera disconnection
  *
@@ -140,7 +170,7 @@ void Camera::disconnect()
 {
 	LOG(Camera, Debug) << "Disconnecting camera " << name_;
 
-	disconnected_ = true;
+	state_ = Disconnected;
 	disconnected.emit(this);
 }
 
@@ -162,10 +192,11 @@ void Camera::disconnect()
  */
 int Camera::acquire()
 {
-	if (acquired_)
+	if (!stateIs(Free))
 		return -EBUSY;
 
-	acquired_ = true;
+	state_ = Acquired;
+
 	return 0;
 }
 
@@ -177,7 +208,10 @@ int Camera::acquire()
  */
 void Camera::release()
 {
-	acquired_ = false;
+	if (!stateIs(Acquired))
+		return;
+
+	state_ = Free;
 }
 
 /**
@@ -191,6 +225,9 @@ void Camera::release()
  */
 const std::vector<Stream *> &Camera::streams() const
 {
+	if (!stateIsAtleast(Free))
+		std::vector<Stream *>{};
+
 	return streams_;
 }
 
@@ -213,7 +250,7 @@ const std::vector<Stream *> &Camera::streams() const
 std::map<Stream *, StreamConfiguration>
 Camera::streamConfiguration(std::vector<Stream *> &streams)
 {
-	if (disconnected_ || !streams.size())
+	if (!stateIsAtleast(Free) || !streams.size())
 		return std::map<Stream *, StreamConfiguration>{};
 
 	return pipe_->streamConfiguration(this, streams);
@@ -244,9 +281,8 @@ int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config)
 {
 	int ret;
 
-	ret = exclusiveAccess();
-	if (ret)
-		return ret;
+	if (!stateIs(Acquired))
+		return -EACCES;
 
 	if (!config.size()) {
 		LOG(Camera, Error)
@@ -284,11 +320,8 @@ int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config)
  */
 int Camera::allocateBuffers()
 {
-	int ret;
-
-	ret = exclusiveAccess();
-	if (ret)
-		return ret;
+	if (!stateIs(Acquired))
+		return -EACCES;
 
 	if (activeStreams_.empty()) {
 		LOG(Camera, Error)
@@ -297,7 +330,7 @@ int Camera::allocateBuffers()
 	}
 
 	for (Stream *stream : activeStreams_) {
-		ret = pipe_->allocateBuffers(this, stream);
+		int ret = pipe_->allocateBuffers(this, stream);
 		if (ret) {
 			LOG(Camera, Error) << "Failed to allocate buffers";
 			freeBuffers();
@@ -313,6 +346,9 @@ int Camera::allocateBuffers()
  */
 void Camera::freeBuffers()
 {
+	if (!stateIs(Acquired))
+		return;
+
 	for (Stream *stream : activeStreams_) {
 		if (!stream->bufferPool().count())
 			continue;
@@ -336,7 +372,7 @@ void Camera::freeBuffers()
  */
 Request *Camera::createRequest()
 {
-	if (exclusiveAccess())
+	if (!stateIsAtleast(Acquired))
 		return nullptr;
 
 	return new Request(this);
@@ -358,13 +394,10 @@ Request *Camera::createRequest()
  */
 int Camera::queueRequest(Request *request)
 {
-	int ret;
+	if (!stateIs(Running))
+		return -EACCES;
 
-	ret = exclusiveAccess();
-	if (ret)
-		return ret;
-
-	ret = request->prepare();
+	int ret = request->prepare();
 	if (ret) {
 		LOG(Camera, Error) << "Failed to prepare request";
 		return ret;
@@ -385,13 +418,18 @@ int Camera::queueRequest(Request *request)
  */
 int Camera::start()
 {
-	int ret = exclusiveAccess();
-	if (ret)
-		return ret;
+	if (!stateIs(Acquired))
+		return -EACCES;
 
 	LOG(Camera, Debug) << "Starting capture";
 
-	return pipe_->start(this);
+	int ret = pipe_->start(this);
+	if (ret)
+		return ret;
+
+	state_ = Running;
+
+	return 0;
 }
 
 /**
@@ -405,24 +443,14 @@ int Camera::start()
  */
 int Camera::stop()
 {
-	int ret = exclusiveAccess();
-	if (ret)
-		return ret;
+	if (!stateIs(Running))
+		return -EACCES;
 
 	LOG(Camera, Debug) << "Stopping capture";
 
 	pipe_->stop(this);
 
-	return 0;
-}
-
-int Camera::exclusiveAccess()
-{
-	if (disconnected_)
-		return -ENODEV;
-
-	if (!acquired_)
-		return -EACCES;
+	state_ = Acquired;
 
 	return 0;
 }
